| ← Back to Main Documentation | Core Systems Index |
Plings Functional Relationships System
This document defines functional relationships—links that describe how objects work together rather than where they are. These relationships complement spatial, ownership, and lifecycle data to give a complete digital twin of every physical object.
📜 Overview
Functional (a.k.a. “compatibility”) relations capture an object’s purpose, compatibility, power flow, control hierarchy, pairing, and replacement logic. Example: “Charger ➡️ powers ➡️ Laptop”, or “Remote ➡️ controls ➡️ TV”.
While spatial predicates answer “Where is it?”, functional predicates answer “How does it interact?”.
Core Design Principles
- Direction Matters — Except When It Doesn’t
• Many relations are directional and come in inverse pairs (POWERS/POWERED_BY).
• Some are symmetric (WORKS_WITH,COMPATIBLE_WITH): order is irrelevant. - Freedom of Modelling — Users decide which predicates make sense; the system never blocks unusual setups.
- Explicit Catalogue — All valid predicates are enumerated (see tables below) and exposed by the API so clients can render friendly names and icons.
- Light-Weight Safeguards — Backend blocks duplicates and self-loops, auto-creates inverse edges if needed, but imposes no semantic rules like “a phone may have only one charger”. Such business logic belongs in higher-level workflows, not the core graph.
Predicate Categories & Vocabulary
Below is the canonical list (v1). The same list exists in code as FUNCTIONAL_PREDICATES (Python) and is mirrored in the GraphQL enum FunctionalPredicate.
1. Compatibility
| Predicate | Inverse | Symmetric? | Description | Examples |
|---|---|---|---|---|
IS_FOR |
ACCEPTS |
No | Designed/intended for target | Remote is_for TV |
ACCEPTS |
IS_FOR |
No | Accepts/works with source | TV accepts Remote |
WORKS_WITH |
— | Yes | Function together as a system | Scanner works_with Computer |
COMPATIBLE_WITH |
— | Yes | Can be used together | USB-C cable compatible_with Phone |
2. Control
| Predicate | Inverse | Symmetric? | Description | Examples |
|---|---|---|---|---|
CONTROLS |
CONTROLLED_BY |
No | Operates/manages target | Remote controls TV |
CONTROLLED_BY |
CONTROLS |
No | Is managed by source | TV controlled_by Remote |
OPERATES |
OPERATED_BY |
No | Activates/runs target | Switch operates Light |
OPERATED_BY |
OPERATES |
No | Activated by source | Light operated_by Switch |
3. Power
| Predicate | Inverse | Symmetric? | Description | Examples |
|---|---|---|---|---|
POWERS |
POWERED_BY |
No | Provides electrical power | Battery powers Flashlight |
POWERED_BY |
POWERS |
No | Receives electrical power | Laptop powered_by Charger |
CHARGES |
CHARGED_BY |
No | Replenishes energy of | Charger charges Phone |
CHARGED_BY |
CHARGES |
No | Energy replenished by | Phone charged_by Charger |
4. Access
| Predicate | Inverse | Symmetric? | Description | Examples |
|---|---|---|---|---|
UNLOCKS |
UNLOCKED_BY |
No | Provides access to target | Key unlocks Door |
UNLOCKED_BY |
UNLOCKS |
No | Is opened by source | Door unlocked_by Key |
OPENS |
OPENED_BY |
No | Opens/activates target | Remote opens Garage Door |
OPENED_BY |
OPENS |
No | Opened/activated by source | Garage Door opened_by Remote |
5. Pairing
| Predicate | Inverse | Symmetric? | Description | Examples |
|---|---|---|---|---|
PAIRS_WITH |
— | Yes | Designed to be used as a pair | Left glove pairs_with Right glove |
MATCHES |
— | Yes | Correspond / form a set | Wine glass matches Dinner plate |
BELONGS_WITH |
— | Yes | Naturally go together | TV remote belongs_with TV |
6. Replacement
| Predicate | Inverse | Symmetric? | Description | Examples |
|---|---|---|---|---|
REPLACES |
REPLACED_BY |
No | Serves as substitute for target | Backup battery replaces Main battery |
REPLACED_BY |
REPLACES |
No | Is substituted by source | Main battery replaced_by Backup |
Data Storage Model
Neo4j
- Each predicate maps to a relationship type in Neo4j, using the same uppercase name.
- Direction =
(:ObjectInstance)-[:POWERS]->(:ObjectInstance). - Symmetric predicates are stored once following a canonical direction (
min(nodeId) → max(nodeId)), enforced in the API layer. - Inverse pairs are auto-created by the API so queries stay simple.
Postgres (Optional Cache)
If UI performance requires, functional relations can be mirrored in a functional_relations table with RLS constraints (see Frontend Views & Permissions doc).
GraphQL API
| Operation | Purpose |
|---|---|
functionalRelations(predicateIn, direction) |
Query relations for an object |
addFunctionalRelation(fromId, toId, predicate) |
Create edge (and inverse) |
removeFunctionalRelation(...) |
Delete edge(s) |
All enums and documentation are introspectable by the client so dropdowns can stay in sync.
Technical Safeguards
| Safeguard | Why | Behaviour |
|---|---|---|
| Self-loop Block | Prevents A POWERS A nonsense |
API rejects if fromId == toId |
| Duplicate Block | Avoid redundant edges & UI clutter | Reject if identical edge exists |
| Inverse Creation | Keeps graph query-friendly | API automatically writes inverse for directional pairs |
| Symmetric Normalisation | Prevents A WORKS_WITH B + B WORKS_WITH A duplicates |
Store once in canonical order |
No other business rules are enforced—users are free to model multiple chargers per phone, etc.
Class-Level Templates & Inheritance
Real-world products are often sold as kits whose components are defined at the class layer—e.g. “Any ✂️ Trimmer is_for any 🔋 Charger of class USB-C Charger.” To avoid constantly writing duplicate edges for every instance, Plings supports query-time inference of functional relations:
- Class edges
Relations may be stored betweenObjectClassnodes, using the same predicate vocabulary ((:ObjectClass)-[:POWERS]->(:ObjectClass)). - Instance edges (explicit)
Users can still create direct instance→instance edges when they need to lock a specific pairing. - Instance edges (inherited / virtual)
When a client asks forfunctionalRelationson anObjectInstance, the resolver returns the union of:
• explicit edges stored on the instance
• virtual edges derived from its class relations.
Resolver Algorithm (simplified)
// 1. explicit
MATCH (src:ObjectInstance {id:$id})-[r:POWERS]->(t:ObjectInstance)
RETURN t, type(r) // plus inherited = false
UNION
// 2. inherited
MATCH (src:ObjectInstance {id:$id})-[:INSTANCE_OF]->(c1:ObjectClass)
MATCH (c1)-[rc:POWERS]->(c2:ObjectClass)
MATCH (t:ObjectInstance)-[:INSTANCE_OF]->(c2)
RETURN DISTINCT t, type(rc) // plus inherited = true
GraphQL Signature
type FunctionalRelationEdge {
target: ObjectInstance!
predicate: FunctionalPredicate!
inherited: Boolean!
sourceClassId: ID # present when inherited = true
}
extend type ObjectInstance {
functionalRelations(
predicateIn: [FunctionalPredicate!]
includeInherited: Boolean = true
): [FunctionalRelationEdge!]!
}
The inherited flag lets UIs render template-based relations in a lighter colour or behind a toggle.
Why Query-Time (Virtual) Inheritance?
- Always current – If a class gains a new relation tomorrow, all its instances reflect that instantly.
- No extra writes – Keeps Neo4j smaller; no need for migration jobs.
- Flexible – Users may still add explicit overrides without worrying about duplicates (explicit > virtual in UI ranking).
If performance profiling ever shows this UNION is a bottleneck, the API can materialise frequently-used combinations with a background job, setting inherited=true so clients can keep treating them the same way.
Example Queries
Cypher – Find what powers a laptop
MATCH (l:ObjectInstance {id:$laptop})<-[:POWERS|CHARGES|POWERED_BY|CHARGED_BY]-(src)
RETURN src
GraphQL – Accessories accepted by a shaver
query($id:ID!){
object(id:$id){
functionalRelations(predicateIn:[ACCEPTS]){ id name }
}
}
Frontend Integration
See frontend_views_and_permissions.md → Object Detail → Functional tab for component breakdown and UX flow. A static list of predicates can be loaded on app start to populate human-friendly labels and icons.
Future Extensions
- Capacity & Compatibility Metadata – e.g.,
rated_voltageproperty on edges. - Temporal Validity – start/end timestamps for temporary replacements.
- Versioning – track changes to relations across time for audit trail.
- Predicate Customisation – allow organisations to define custom functional predicates scoped to their tenant (requires namespace strategy).
Functional relationships, together with spatial, ownership, and lifecycle data, allow Plings to represent both where every object is and how it cooperates with the things around it—forming a richly connected, queryable universal object graph.