Neo4j Core Schema (Graph & Postgres Reference)

Created: Tue 29 Jul 2025 06:49:39 CEST
Document Version: 1.0 - Initial documentation with timestamp
Security Classification: Internal Technical Documentation
Target Audience: Backend Developers, Database Administrators
Author: Paul Wisén

This document defines the core data schema for the Universal Object Graph System, designed for use with Neo4j. The schema is built around a property graph model of nodes and relationships.

Graph Model Overview

The system models the physical world using a graph containing several key node labels and relationship types.

  • Nodes: Represent entities like object blueprints (ObjectClass), physical items (ObjectInstance), and identifiers (PlingsIdentifier).
  • Relationships: Represent how these entities are connected, such as an instance being an INSTANCE_OF a class, a component being PART_OF another, or an object being IN a container.

Node Reference

1. :ObjectClass Node

Represents a template or definition of an object.

Property Type Description Example
name String Required. The unique name of the class. "Mountain Bike"
description String A detailed description of the object class. "A bicycle designed for off-road cycling."
version Integer The version number for the class definition. 2
owner String The entity that defined or owns this class. "ACME Inc."
visibility String Access control for the class (“public”, “private”). "public"
capabilities List<String> A list of potential uses for this object type. ["off-road", "sport"]
anchor_key String The public cryptographic anchor key from the HD Wallet. "xpub..."
path_template String The schema for interpreting the HD Wallet derivation path. "model/factory/instance"

2. :ObjectInstance Node

Represents a specific, physical object in the real world.

Property Type Description Example
statuses List<String> The current state(s) of the physical object. ["for_sale", "broken"]
owner String The user or organization that owns this instance. "user:12345"

3. :PlingsIdentifier Node

Represents a physical or digital identifier (e.g., QR code, NFC tag).

Property Type Description Example
key String Required. The unique identifier value. "plg:44-..."
identifier_type String The medium of the identifier. "QR"
status String The state of the identifier itself. "active"
is_one_time_use Boolean Flag for OTP-style verification identifiers. true

4. :ScanEvent Node

Represents a single scan event of a PlingsIdentifier, creating a historical audit trail.

Property Type Description Example
timestamp DateTime Required. The exact time of the scan. 2024-07-30T10:00:00Z
latitude Float The GPS latitude of the scan location. 34.0522
longitude Float The GPS longitude of the scan location. -118.2437
scanning_user String The user who performed the scan. "user:54321"
owner_at_time_of_scan String The recorded owner at the moment of the scan. "user:12345"

Relationship Reference

Core Relationships

Start Node Type End Node Description
:ObjectInstance INSTANCE_OF :ObjectClass Links a physical item to its blueprint.
:ObjectClass SUBCLASS_OF :ObjectClass Creates the class hierarchy (e.g., Mountain Bike is a subclass of Bicycle).
:ObjectInstance PART_OF :ObjectInstance Defines the physical composition of complex objects (a wheel is part of a bike).
:PlingsIdentifier IDENTIFIES :ObjectInstance Links an identifier to the specific object it represents.
:ScanEvent SCANNED :PlingsIdentifier Records that a specific identifier was scanned in an event.

Spatial Relationships

These relationship types model the physical world. The authoritative list of spatial predicates is maintained in the Supabase spatial_predicates table, and these predicates become Neo4j relationship types.

Dual Spatial Relationship System (from spatial_predicates table)

The spatial system uses dual predicates to separately track current and expected locations. The authoritative list is maintained in the Supabase spatial_predicates table.

Current Location Relationships (where objects are now):

Start Node Type End Node Parent Relationship Description
:ObjectInstance CURRENT_IN :ObjectInstance ✅ Yes Subject is currently inside target
:ObjectInstance CURRENT_ON :ObjectInstance ✅ Yes Subject is currently on surface of target
:ObjectInstance CURRENT_LEFT_OF :ObjectInstance ❌ No Subject is currently to the left of target
:ObjectInstance CURRENT_RIGHT_OF :ObjectInstance ❌ No Subject is currently to the right of target
:ObjectInstance CURRENT_ABOVE :ObjectInstance ❌ No Subject is currently above target
:ObjectInstance CURRENT_UNDER :ObjectInstance ❌ No Subject is currently under target
:ObjectInstance CURRENT_NEXT_TO :ObjectInstance ❌ No Subject is currently next to target
:ObjectInstance CURRENT_ATTACHED_TO :ObjectInstance ❌ No Subject is currently attached to target

Normal/Expected Location Relationships (where objects should be):

Start Node Type End Node Parent Relationship Description
:ObjectInstance NORMAL_IN :ObjectInstance ✅ Yes Subject should normally be inside target
:ObjectInstance NORMAL_ON :ObjectInstance ✅ Yes Subject should normally be on surface of target
:ObjectInstance NORMAL_LEFT_OF :ObjectInstance ❌ No Subject should normally be to the left of target
:ObjectInstance NORMAL_RIGHT_OF :ObjectInstance ❌ No Subject should normally be to the right of target
:ObjectInstance NORMAL_ABOVE :ObjectInstance ❌ No Subject should normally be above target
:ObjectInstance NORMAL_UNDER :ObjectInstance ❌ No Subject should normally be under target
:ObjectInstance NORMAL_NEXT_TO :ObjectInstance ❌ No Subject should normally be next to target
:ObjectInstance NORMAL_ATTACHED_TO :ObjectInstance ❌ No Subject should normally be attached to target

Misplacement Detection Examples:

// Box is on wrong pallet
(:Box)-[:CURRENT_ON]->(:Pallet_A)
(:Box)-[:NORMAL_ON]->(:Pallet_B)

// Keys are contained but should be hanging
(:Keys)-[:CURRENT_IN]->(:Drawer)
(:Keys)-[:NORMAL_ON]->(:KeyHook)

// Monitor on wrong side of computer
(:Monitor)-[:CURRENT_LEFT_OF]->(:Computer)
(:Monitor)-[:NORMAL_RIGHT_OF]->(:Computer)

Note: Legacy unprefixed predicates (IN, ON, LEFT_OF, etc.) are deprecated in favor of the dual system.

Functional Relationships

These relationship types describe how objects interact rather than where they are located. They come from the authoritative catalogue in functional_relationships_system.md and are stored as uppercase relationship types in Neo4j (directional pairs are stored both ways; symmetric predicates are stored once in canonical order).

Category Type (Predicate) Inverse Symmetric? Description Example
Physical Composition PART_OF HAS_PART No Component is part of a larger assembly or set (:Wheel)-[:PART_OF]->(:Bike)
Compatibility IS_FOR ACCEPTS No Subject is designed for target (:Remote)-[:IS_FOR]->(:TV)
  ACCEPTS IS_FOR No Target accepts subject (:TV)-[:ACCEPTS]->(:Remote)
  WORKS_WITH Yes Designed to function together (:Scanner)-[:WORKS_WITH]-(:Computer)
Control CONTROLS CONTROLLED_BY No Subject operates target (:Remote)-[:CONTROLS]->(:TV)
  OPERATES OPERATED_BY No Activates / runs target (:Switch)-[:OPERATES]->(:Light)
Power POWERS POWERED_BY No Subject provides electrical power (:Battery)-[:POWERS]->(:Flashlight)
  CHARGES CHARGED_BY No Replenishes energy of target (:Charger)-[:CHARGES]->(:Phone)
Access UNLOCKS UNLOCKED_BY No Grants access to target (:Key)-[:UNLOCKS]->(:Door)
Pairing PAIRS_WITH Yes Two items form an intended pair (:LeftGlove)-[:PAIRS_WITH]-(:RightGlove)
Replacement REPLACES REPLACED_BY No Subject serves as substitute (:BackupBattery)-[:REPLACES]->(:MainBattery)

Note: Functional predicates can also be created between ObjectClass nodes to act as templates. The API resolver automatically merges explicit instance-level edges with class-level edges (marked inherited = true) so that every object gains the functional context of its class hierarchy without data duplication.

The PART_OF relationship is a special case used for both physical assembly (a wheel is part of a bike) and for defining “Set Objects” where a conceptual item contains other items (a “Glove Pair Set” has a left and right glove).

Example Cypher

// Find everything that powers a given laptop (instance-level + class templates)
MATCH (l:ObjectInstance {id:$laptop})<-[:POWERS|POWERED_BY|CHARGES|CHARGED_BY]-(src)
RETURN DISTINCT src.id, type(relationships(src,l)) AS predicate;

Supabase (Postgres) Schema

Tables

  1. profiles
    • id: uuid, not nullable
    • full_name: text, nullable
    • avatar_url: text, nullable
    • updated_at: timestamp with time zone, nullable, default now()
  2. organizations
    • id: uuid, not nullable, default gen_random_uuid()
    • name: text, not nullable
    • created_at: timestamp with time zone, nullable, default now()
    • created_by: uuid, nullable
    • type: text, not nullable, default 'Individual'::text
    • description: text, nullable
  3. organization_members
    • organization_id: uuid, not nullable
    • user_id: uuid, not nullable
    • role: text, not nullable
    • joined_at: timestamp with time zone, nullable, default now()
  4. object_instances
    • id: uuid, not nullable
    • neo4j_id: text, nullable
    • name: text, not nullable
    • description: text, nullable
    • owner_organization_id: uuid, not nullable
    • created_by: uuid, not nullable
    • created_at: timestamp with time zone, nullable, default now()
    • updated_at: timestamp with time zone, nullable, default now()
    • status: text, nullable
    • main_image_url: text, nullable
    • last_scanned_at: timestamp with time zone, nullable
    • last_scanned_by: uuid, nullable
  5. object_images
    • id: uuid, not nullable
    • object_instance_id: uuid, not_nullable

This file was introduced to eliminate ambiguous names like schema.md now that the project uses both Neo4j and Supabase databases.

Last Updated: Thu Jul 10 09:18:47 CEST 2025 - Standardized PlingsIdentifier relationship from POINTS_TO to IDENTIFIES for better semantic clarity