rvf 0.1.0

Rust implementation of the ValueFlows vocabulary for distributed economic networks
Documentation
//! Flows - the core abstraction for economic activity
//!
//! Flows represent the movement of value through an economic network.
//! They can be intents, commitments, or events.

use crate::actions::ActionType;
use crate::measures::Measure;
use chrono::{DateTime, Utc};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The type of flow
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum FlowType {
    /// An intent (offer or request)
    Intent,
    /// A commitment (promise)
    Commitment,
    /// An economic event (observation)
    Event,
    /// A claim (demand for reciprocity)
    Claim,
    /// A recipe flow (template)
    RecipeFlow,
}

/// A generic flow representation
///
/// This provides a unified view of intents, commitments, and events
/// for algorithms that need to work with all types of flows.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Flow {
    /// Unique identifier
    pub id: String,
    /// The type of flow
    pub flow_type: FlowType,
    /// The action
    pub action: ActionType,
    /// The providing agent
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub provider: Option<String>,
    /// The receiving agent
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub receiver: Option<String>,
    /// Resource quantity
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub resource_quantity: Option<Measure>,
    /// Effort quantity
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub effort_quantity: Option<Measure>,
    /// The resource specification
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub resource_conforms_to: Option<String>,
    /// The actual resource
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub resource_inventoried_as: Option<String>,
    /// Reference to process this is input to
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub input_of: Option<String>,
    /// Reference to process this is output from
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub output_of: Option<String>,
    /// When the flow happens/happened
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub has_point_in_time: Option<DateTime<Utc>>,
    /// Optional note
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub note: Option<String>,
}

impl Flow {
    /// Create a new flow
    pub fn new(id: impl Into<String>, flow_type: FlowType, action: ActionType) -> Self {
        Self {
            id: id.into(),
            flow_type,
            action,
            provider: None,
            receiver: None,
            resource_quantity: None,
            effort_quantity: None,
            resource_conforms_to: None,
            resource_inventoried_as: None,
            input_of: None,
            output_of: None,
            has_point_in_time: None,
            note: None,
        }
    }

    /// Check if this is an input flow
    pub fn is_input(&self) -> bool {
        self.input_of.is_some()
    }

    /// Check if this is an output flow
    pub fn is_output(&self) -> bool {
        self.output_of.is_some()
    }

    /// Check if this is a past flow (event)
    pub fn is_observation(&self) -> bool {
        matches!(self.flow_type, FlowType::Event)
    }

    /// Check if this is a planned flow
    pub fn is_planned(&self) -> bool {
        matches!(
            self.flow_type,
            FlowType::Intent | FlowType::Commitment | FlowType::RecipeFlow
        )
    }

    /// Set the provider
    pub fn with_provider(mut self, agent_id: impl Into<String>) -> Self {
        self.provider = Some(agent_id.into());
        self
    }

    /// Set the receiver
    pub fn with_receiver(mut self, agent_id: impl Into<String>) -> Self {
        self.receiver = Some(agent_id.into());
        self
    }

    /// Set the resource quantity
    pub fn with_resource_quantity(mut self, quantity: Measure) -> Self {
        self.resource_quantity = Some(quantity);
        self
    }

    /// Set the resource specification
    pub fn with_resource_conforms_to(mut self, spec_id: impl Into<String>) -> Self {
        self.resource_conforms_to = Some(spec_id.into());
        self
    }

    /// Set the input process
    pub fn with_input_of(mut self, process_id: impl Into<String>) -> Self {
        self.input_of = Some(process_id.into());
        self
    }

    /// Set the output process
    pub fn with_output_of(mut self, process_id: impl Into<String>) -> Self {
        self.output_of = Some(process_id.into());
        self
    }
}

/// Direction of flow traversal
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlowDirection {
    /// Forward direction (tracking)
    Forward,
    /// Backward direction (tracing)
    Backward,
}

/// A node in a flow graph
#[derive(Debug, Clone)]
pub struct FlowNode {
    /// The flow at this node
    pub flow: Flow,
    /// The depth in the traversal
    pub depth: usize,
    /// The parent node ID (if any)
    pub parent: Option<String>,
}

impl FlowNode {
    /// Create a new flow node
    pub fn new(flow: Flow, depth: usize, parent: Option<String>) -> Self {
        Self { flow, depth, parent }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::measures::Unit;

    #[test]
    fn test_flow_creation() {
        let flow = Flow::new("flow-001", FlowType::Event, ActionType::Produce)
            .with_provider("agent-001")
            .with_receiver("agent-001")
            .with_resource_quantity(Measure::new(100, Unit::Kilogram));

        assert_eq!(flow.id, "flow-001");
        assert!(flow.is_observation());
        assert_eq!(flow.provider, Some("agent-001".to_string()));
    }

    #[test]
    fn test_flow_classification() {
        let intent = Flow::new("flow-001", FlowType::Intent, ActionType::Transfer);
        assert!(intent.is_planned());
        assert!(!intent.is_observation());

        let event = Flow::new("flow-002", FlowType::Event, ActionType::Transfer);
        assert!(event.is_observation());
        assert!(!event.is_planned());
    }

    #[cfg(feature = "serde")]
    #[test]
    fn test_flow_serialization() {
        let flow = Flow::new("flow-001", FlowType::Commitment, ActionType::Produce)
            .with_provider("agent-001")
            .with_resource_quantity(Measure::new(50, Unit::Each));

        let json = serde_json::to_string(&flow).unwrap();
        let parsed: Flow = serde_json::from_str(&json).unwrap();
        assert_eq!(flow.id, parsed.id);
        assert_eq!(flow.flow_type, parsed.flow_type);
    }
}