wifi-densepose-worldgraph 0.3.1

ADR-139 — WorldGraph environmental digital twin (typed petgraph) for RuView
Documentation

wifi-densepose-worldgraph

The environmental digital twin for RF sensing — a typed, evidence-tracked graph of a building and the people in it.

crates.io docs.rs

Part of the RuView / WiFi-DensePose project. Implements ADR-139.


What it is (plain language)

When you sense a space with WiFi/RF (people, motion, vital signs), you get a firehose of frames. What you actually want is a living map: which rooms exist, where the walls and doorways are, which sensors watch which zones, where each person is right now, and why the system believes that — with enough structure to reason over and enough provenance to trust.

wifi-densepose-worldgraph is that map. It's a typed graph (built on petgraph):

  • Nodes are real things — Room, Zone, Wall, Doorway, Sensor, RfLink, PersonTrack, ObjectAnchor, Event, and SemanticState (a belief).
  • Edges are typed relations — Observes, LocatedIn, AdjacentTo, Supports, Contradicts, DerivedFrom, PrivacyLimitedBy.

It stores fused beliefs, not raw frames — it sits downstream of signal fusion and upstream of the semantic/agent layer. Every belief (SemanticState) is required to carry provenance: the signal evidence, the model, the calibration id, and the privacy decision that produced it. That's enforced structurally, so "where did this conclusion come from?" always has an answer.

Why a graph (and not an occupancy grid or an event log)?

Approach Good at Misses
Raw event log append-only history, audit no structure; can't ask "who's in the kitchen?" without re-deriving it
Occupancy grid / voxels dense geometry, ML input no identity, no relations, no provenance, no semantics
Scene graph (this crate) relations, identity, semantics, provenance, privacy not a dense field — pair it with a grid for ML (see wifi-densepose-worldmodel)

The graph is the symbolic, interpretable layer. It answers relational questions ("is this person in a zone observed by sensor X?", "are these two beliefs contradictory?") in O(neighbors), and it keeps the why attached to every what.

Features

  • 🧱 Typed node/edge model — a closed enum schema (serde-tagged) → deterministic, schema-versioned wire format.
  • 🧭 Geometry in ENU meters — rooms/zones/walls/doorways carry East-North-Up bounds; walls carry rf_attenuation_db.
  • 🧠 Beliefs with mandatory provenanceSemanticStateSemanticProvenance { signal evidence, model, calibration_id, privacy_decision }.
  • 🔀 Evidence reasoning built inSupports / Contradicts / DerivedFrom edges let you score and challenge conclusions, not just store them.
  • 🔒 Privacy as a first-class edgePrivacyLimitedBy + apply_privacy_mode() roll up what a given mode/action is allowed to see.
  • 💾 Deterministic JSON persistenceto_json / from_json (the RVF payload), schema-versioned.
  • 🚫 #![forbid(unsafe_code)], missing_docs = warn. Pure Rust, no async, edge-deployable (builds clean on aarch64 — runs on a Raspberry Pi).

Install

[dependencies]
wifi-densepose-worldgraph = "0.3"

Usage

use wifi_densepose_worldgraph::{WorldGraph, WorldNode, WorldEdge, ZoneBoundsEnu};
// (GeoRegistration comes from wifi-densepose-geo — it anchors ENU to a real lat/lon origin)

let mut wg = WorldGraph::new(registration);

// Add a room and a sensor that observes it.
let living_room = wg.upsert_node(WorldNode::Room {
    id: Default::default(),
    area_id: Some("living_room".into()),
    name: "Living Room".into(),
    bounds_enu: ZoneBoundsEnu { /**/ },
    floor: 0,
});
let sensor = wg.upsert_node(/* WorldNode::Sensor { … } */);
wg.add_edge(sensor, living_room, WorldEdge::Observes { quality: 0.9, last_seen_unix_ms: now });

// Query relations.
let watched = wg.observed_by(sensor);          // what this sensor sees
let room = wg.room_for_area("living_room");    // area_id → room node

// Record a belief WITH provenance, and a contradiction against it.
wg.add_semantic_state(/* state + SemanticProvenance */);
wg.add_contradiction(belief_a, belief_b, /* magnitude */, "two sensors disagree");

// Privacy rollup for a mode/action, then persist.
let rollup = wg.apply_privacy_mode("HOME", "occworld_inference", |node| /* allow? */ true);
let bytes = wg.to_json()?;                      // RVF payload
let restored = WorldGraph::from_json(&bytes)?;

Technical details

  • Backing store: petgraph::StableDiGraph (stable indices across removals) wrapped as WorldGraph.
  • Identity: every node has a WorldId; upsert_node is idempotent on identity.
  • Snapshots: snapshot()WorldGraphSnapshot (a serializable point-in-time view) with a PrivacyRollup.
  • Schema versioning: SCHEMA_VERSION is embedded in the JSON; the closed enum model means readers fail fast on incompatible payloads rather than silently mis-parsing.
  • Coordinates: ENU (East/North/Up) meters relative to a GeoRegistration origin (wifi-densepose-geo), so the twin can be georeferenced to a real building.
  • Position in the pipeline: fusion (ADR-137) → WorldGraph (ADR-139) → semantic/agent layer (ADR-140) → eval harness (ADR-145). For forward prediction (where will people be next?), pair it with wifi-densepose-worldmodel, which turns PersonTrack history into predicted occupancy + trajectory priors.

Related crates

Crate Role
wifi-densepose-worldmodel Forward prediction — occupancy world model over this graph's tracks
wifi-densepose-geo Geospatial registration (ENU ↔ lat/lon, DEM, OSM)

License

Licensed as the parent project. See the repository.