///|
/// A lightweight relationship graph for customer-account analysis.
pub enum EntityKind {
CustomerNode
AccountNode
TransactionNode
} derive(Eq, Debug)
///|
pub(all) struct Entity {
id : String
kind : EntityKind
}
///|
pub(all) struct Relationship {
from_id : String
to_id : String
label : String
weight : Int
}
///|
pub(all) struct TransactionGraph {
entities : Array[Entity]
relationships : Array[Relationship]
}
///|
pub fn TransactionGraph::empty() -> TransactionGraph {
{ entities: [], relationships: [] }
}
///|
pub fn TransactionGraph::add_entity(
self : TransactionGraph,
entity : Entity,
) -> TransactionGraph {
let entities : Array[Entity] = []
for old in self.entities {
if old.id != entity.id {
entities.push(old)
}
}
entities.push(entity)
{ ..self, entities, }
}
///|
pub fn TransactionGraph::add_relationship(
self : TransactionGraph,
relationship : Relationship,
) -> TransactionGraph {
{ ..self, relationships: push_relationship(self.relationships, relationship) }
}
///|
fn push_relationship(
old : Array[Relationship],
item : Relationship,
) -> Array[Relationship] {
let result : Array[Relationship] = []
for value in old {
result.push(value)
}
result.push(item)
result
}
///|
pub fn build_graph(transactions : Array[Transaction]) -> TransactionGraph {
let graph = TransactionGraph::empty()
let mut current = graph
for tx in transactions {
current = current.add_entity({ id: tx.customer_id, kind: CustomerNode })
current = current.add_entity({ id: tx.account_id, kind: AccountNode })
current = current.add_entity({ id: tx.id, kind: TransactionNode })
current = current.add_relationship({
from_id: tx.customer_id,
to_id: tx.account_id,
label: "owns",
weight: 1,
})
current = current.add_relationship({
from_id: tx.account_id,
to_id: tx.id,
label: "used-by",
weight: tx.amount,
})
}
current
}
///|
pub fn TransactionGraph::degree(self : TransactionGraph, id : String) -> Int {
let mut count = 0
for edge in self.relationships {
if edge.from_id == id || edge.to_id == id {
count += 1
}
}
count
}
///|
pub fn TransactionGraph::neighbors(
self : TransactionGraph,
id : String,
) -> Array[String] {
let result : Array[String] = []
for edge in self.relationships {
if edge.from_id == id {
result.push(edge.to_id)
}
if edge.to_id == id {
result.push(edge.from_id)
}
}
result
}
///|
pub fn high_degree_entities(
graph : TransactionGraph,
threshold : Int,
) -> Array[Entity] {
let result : Array[Entity] = []
for entity in graph.entities {
if graph.degree(entity.id) >= threshold {
result.push(entity)
}
}
result
}
///|
pub fn shared_accounts(transactions : Array[Transaction]) -> Array[String] {
let result : Array[String] = []
for tx in transactions {
let customers : Array[String] = []
for candidate in transactions {
if candidate.account_id == tx.account_id &&
!contains_string(customers, candidate.customer_id) {
customers.push(candidate.customer_id)
}
}
if customers.length() > 1 && !contains_string(result, tx.account_id) {
result.push(tx.account_id)
}
}
result
}
///|
fn contains_string(items : Array[String], wanted : String) -> Bool {
let mut found = false
for item in items {
if item == wanted {
found = true
}
}
found
}