use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use super::epoch::GrammarEpoch;
use super::lsg::LatentGraph;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ObservationNode {
pub id: String,
pub kind: ObservationNodeKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fingerprint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bbox: Option<[f64; 4]>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub attrs: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ObservationNodeKind {
Mark,
Group,
Anchor,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ObservationEdge {
pub from: String,
pub to: String,
pub rel: String,
#[serde(default = "one")]
pub confidence: f64,
}
fn one() -> f64 {
1.0
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ObservationGraph {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub epoch: Option<GrammarEpoch>,
pub is_doldskrift: bool,
pub nodes: Vec<ObservationNode>,
pub edges: Vec<ObservationEdge>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latent_carrier: Option<LatentGraph>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
impl ObservationGraph {
pub fn from_latent_carrier(graph: LatentGraph) -> Self {
let epoch = graph.meta.epoch;
Self {
epoch: Some(epoch),
is_doldskrift: true,
nodes: vec![ObservationNode {
id: "obs0".into(),
kind: ObservationNodeKind::Group,
fingerprint: Some("DSK-NEURAL-CARRIER".into()),
bbox: Some([0.0, 0.0, 1.0, 1.0]),
attrs: BTreeMap::new(),
}],
edges: vec![],
latent_carrier: Some(graph),
notes: Some("Synthetic carrier observation — not a camera reconstruction".into()),
}
}
pub fn not_doldskrift() -> Self {
Self {
epoch: None,
is_doldskrift: false,
nodes: vec![],
edges: vec![],
latent_carrier: None,
notes: Some("No Doldskrift structure detected".into()),
}
}
}