pcalc.entity
Introduction
We assume an externally defined domain $D$ with its own structure, topologies, constraints, and semantics. While these aspects of the domain influence the kinds of presence assertions that can be expressed over it, the Presence Calculus itself remains domain-agnostic.
Apart from some weak assumptions about what constitutes a measurable quantity in the domain, the calculus is solely concerned with presence over $D$, and the constructs and calculations derived from them.
A presence models the concept that “an element was present continuously in a boundary from time $t_0$ to $t_1$, with a certain measurable value.”
Elements and Boundaries
It is useful to distinguish Elements ($E$) and Boundaries ($B$) as subsets of the domain $D$:
- Some entities act as "things" that are present (Elements).
- Others act as "places" or contexts in which presence occurs (Boundaries).
$$ E = \{ e_1, e_2, \dots, e_n \} \subset D $$
$$ B = \{ b_1, b_2, \dots, b_m \} \subset D $$
There are no constraints on what an element or boundary can be. These roles are application-defined, context-dependent, and scoped to a particular set of presence assertions under analysis.
The same domain entity may be an element in one presence assertion and a boundary in another.
Examples
In traffic network, the locations and road segments of the road network might be natural boundaries and vehicles might be elements.
In a business value network, the participants in the network would be natural boundaries and value exchanges between the participants would be natural elements.
In a customer relationship management context, the boundaries might be customer segments and elements might be customers or prospects.
In a process management context, the boundaries might be process states and the elements might be processing items.
In an organizational design context, the organization units might be the boundaries and the elements might be job functions.
In a software development context, a boundary might be code branch and an element might be a code change.
Please note that these are illustrative examples there may be other choices that can be made even in these same domains.
Observers
In addition to elements and boundaries, we have a set of observers from the domain, who assert presences. $$ O = \{ o_1, o_2, \dots, o_n \} \subset D $$ Again there are no constraints on what an observer is other than that it is an entity in the domain. it could be something that also appears as either an entity or a boundary in the same presence assertion (reflexive assertions). It could also be a distinguished subset of entities in the domain.
In general, you are free to model elements, boundaries and observers as you wish, provided that there are meaningful domain semantics you can assign to a statement like "An observer $o$ asserted at time $t_2$ that an element $e$ was present in a boundary $b$ from time $t_0$ to $t_1$."
Elements, Boundaries and Observers are simply placeholders for entity roles in a presence assertion.
Indeed, the utility of the calculus only extends as far as one can make inferences using the machinery of the calculus that have useful semantics when mapped back into the domain $D$.
Therefore, modeling choices are critical.
Entity Module
The connection between the domain and the calculus is established via the
EntityProtocol
, which declares the minimal contract a domain entity in
$D$ must satisfy in order to participate in presence assertions over $D$ as an
element or a boundary.
The remaining classes in the module are various utility classes that are provided to simplify integrating a domain model to the presence calculus.
1# -*- coding: utf-8 -*- 2# Copyright (c) 2025 Krishna Kumar 3# SPDX-License-Identifier: MIT 4""" 5## Introduction 6 7We assume an externally defined domain $D$ with its own structure, 8topologies, constraints, and semantics. While these aspects of the domain 9influence the kinds of presence assertions that can be expressed over it, 10the Presence Calculus itself remains domain-agnostic. 11 12Apart from some weak assumptions about what constitutes a measurable quantity 13in the domain, the calculus is solely concerned with 14[presence](./presence.html) over $D$, and the constructs and 15calculations derived from them. 16 17A [presence](./presence.html) models the concept that “an element was present 18continuously in a boundary from time $t_0$ to $t_1$, with a certain measurable value.” 19 20### Elements and Boundaries 21 22It is useful to distinguish Elements ($E$) 23and Boundaries ($B$) as subsets of the domain $D$: 24 25- Some entities act as "things" that are present (Elements). 26- Others act as "places" or contexts in which presence occurs (Boundaries). 27 28$$ 29E = \\\\{ e_1, e_2, \dots, e_n \\\\} \subset D 30$$ 31 32$$ 33B = \\\\{ b_1, b_2, \dots, b_m \\\\} \subset D 34$$ 35 36There are no constraints on what an element or boundary can be. These roles 37are application-defined, context-dependent, and scoped to a particular set of 38presence assertions under analysis. 39 40The same domain entity may be an element in one presence 41assertion and a boundary in another. 42 43 44#### Examples 45 46- In traffic network, the locations and 47road segments of the road network might be natural boundaries and vehicles might be elements. 48 49- In a business value network, the participants in the network would be natural boundaries and value exchanges 50between the participants would be natural elements. 51 52- In a customer relationship management context, the boundaries might be customer segments and elements might be customers 53or prospects. 54 55- In a process management context, the boundaries might be process states and the elements might be processing items. 56 57- In an organizational design context, the organization units might be the boundaries and the elements might be job functions. 58 59- In a software development context, a boundary might be code branch and an element might be a code change. 60 61Please note that these are illustrative examples there may be other choices that can be made even in these same domains. 62 63### Observers 64 65In addition to elements and boundaries, we have a set of observers from the domain, who *assert* presences. 66$$ 67O = \\\\{ o_1, o_2, \dots, o_n \\\\} \subset D 68$$ 69Again there are no constraints on what an observer is other than that it is an entity in the domain. 70it could be something that also appears as either an entity or a boundary in the same presence assertion 71(reflexive assertions). It could also be a distinguished subset of entities in the domain. 72 73 74In general, you are free to model elements, boundaries and observers as you wish, provided that 75there are meaningful domain semantics you can assign to a statement 76like "An observer $o$ asserted at time $t_2$ that an element $e$ 77was present in a boundary $b$ from time $t_0$ to $t_1$." 78 79Elements, Boundaries and Observers are simply placeholders for entity roles in a presence assertion. 80 81Indeed, the utility of the calculus only extends as far as one can 82make inferences using the machinery of the calculus that have useful semantics when mapped back into the 83domain $D$. 84 85Therefore, modeling choices are critical. 86 87## Entity Module 88 89The connection between the domain and the calculus is established via the 90`EntityProtocol`, which declares the minimal contract a domain entity in 91$D$ must satisfy in order to participate in presence assertions over $D$ as an 92element or a boundary. 93 94The remaining classes in the module are various utility classes that are provided 95to simplify integrating a domain model to the presence calculus. 96 97""" 98from __future__ import annotations 99 100import uuid 101from typing import Protocol, runtime_checkable, Dict, Any, Optional 102 103 104@runtime_checkable 105class EntityProtocol(Protocol): 106 """ 107 The interface contract for a domain entity to participate in a presence assertion. 108 109 Each entity requires only a unique identifier, a user-facing name. 110 111 Optional metadata may be provided and exposes specific attributes of the domain 112 entities that are relevant when interpreting or manipulating the results 113 of an analysis using the machinery of the calculus. 114 115 See `Entity` for an example of a concrete implementation. 116 """ 117 __init__ = None # type: ignore 118 119 @property 120 def id(self) -> str: 121 """ 122 A stable, unique identifier for the entity. 123 Used for indexing and identity. 124 Defaults to a uuid.uuid4(). 125 """ 126 ... 127 128 @property 129 def name(self) -> str: 130 """ 131 A user facing name for the entity, defaults to the id if None. 132 """ 133 ... 134 135 @name.setter 136 def name(self, name: str) -> None: 137 """Setter for name""" 138 ... 139 140 @property 141 def metadata(self) -> Dict[str, Any]: 142 """ 143 Optional extensible key-value metadata. 144 """ 145 ... 146 147 148class EntityMixin: 149 """A mixin class that can be used to inject common shared behavior 150 of objects satisfying the EntityProtocol into existing domain entities. 151 """ 152 153 def summary(self: EntityProtocol) -> str: 154 """ 155 Return a human-readable summary based on id and metadata. 156 """ 157 meta = getattr(self, "metadata", {}) 158 if not meta: 159 return f"Element[{self.id}] name = {self.name} (no metadata)" 160 formatted = ", ".join(f"{k}={v!r}" for k, v in meta.items()) 161 return f"Element[{self.id}] name = {self.name} {{{formatted}}}" 162 163 164class EntityView(EntityMixin): 165 """ 166 A view class that allows domain objects to behave like entities 167 168 ```python 169 class Customer: 170 def __init__(self, id, name, segment): 171 self._id = id 172 self._name = name 173 self._segment = segment 174 175 @property 176 def id(self): 177 return self._id 178 179 @property 180 def name(self): 181 return self._name 182 183 @property 184 def metadata(self): 185 return {"segment": self._segment} 186 ``` 187 188 You can wrap it with `EntityView` to use it with presence-related infrastructure: 189 190 ```python 191 customer = Customer("cust-001", "Alice Chen", "Enterprise") 192 entity = EntityView(customer) 193 194 print(entity.id) # cust-001 195 print(entity.name) # Alice Chen 196 print(entity.metadata) # {'segment': 'Enterprise'} 197 print(entity.summary()) # Element[cust-001] name = Alice Chen {segment='Enterprise'} 198 ``` 199 """ 200 201 __slots__ = ("_id", "_name", "_metadata") 202 203 def __init__(self, base: EntityProtocol): 204 self._base = base 205 206 @property 207 def id(self) -> str: 208 return self._base.id 209 210 @property 211 def name(self) -> Optional[str]: 212 return self._base.name 213 214 @property 215 def metadata(self) -> Dict[str, Any]: 216 return self._base.metadata 217 218 219class Entity(EntityMixin, EntityProtocol): 220 """A default implementation of fully functional entity. 221 222 ```python 223 elements = [ 224 Entity(id="cust-001", name="Alice Chen", metadata={"type": "customer"}), 225 Entity(id="cust-002", name="Bob Gupta", metadata={"type": "customer"}), 226 Entity(id="pros-003", name="Dana Rivera", metadata={"type": "prospect"}), 227 ] 228 boundaries = [ 229 Entity(id="seg-enterprise", name="Enterprise Segment", metadata={"region": "NA"}), 230 Entity(id="seg-smb", name="SMB Segment", metadata={"region": "EMEA"}), 231 Entity(id="seg-dormant", name="Flight Risk", metadata={"status": "inactive", "last login": "2024-06-01"}), 232 Entity(id="seg-prospect", name="Active Prospects", metadata={"status": "demo given"}) 233 ] 234 235 for e in elements: 236 print(f"Element: {e.summary()}") 237 238 for b in boundaries: 239 print(f"Boundary: {b.summary()}") 240 ``` 241 """ 242 __slots__ = ("_id", "_name", "_metadata") 243 244 # noinspection PyProtocol 245 def __init__(self, id: Optional[str] = None, name: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None): 246 self._id: str = id or str(uuid.uuid4()) 247 self._name: str = name or self.id 248 self._metadata: Dict[str, Any] = metadata or {} 249 250 @property 251 def id(self) -> str: 252 return self._id 253 254 # noinspection PyProtocol 255 @property 256 def name(self) -> str: 257 return self._name 258 259 @name.setter 260 def name(self, name: str) -> None: 261 self._name = name 262 263 @property 264 def metadata(self) -> Dict[str, Any]: 265 return self._metadata 266 267 def __str__(self) -> str: 268 return self.summary()
105@runtime_checkable 106class EntityProtocol(Protocol): 107 """ 108 The interface contract for a domain entity to participate in a presence assertion. 109 110 Each entity requires only a unique identifier, a user-facing name. 111 112 Optional metadata may be provided and exposes specific attributes of the domain 113 entities that are relevant when interpreting or manipulating the results 114 of an analysis using the machinery of the calculus. 115 116 See `Entity` for an example of a concrete implementation. 117 """ 118 __init__ = None # type: ignore 119 120 @property 121 def id(self) -> str: 122 """ 123 A stable, unique identifier for the entity. 124 Used for indexing and identity. 125 Defaults to a uuid.uuid4(). 126 """ 127 ... 128 129 @property 130 def name(self) -> str: 131 """ 132 A user facing name for the entity, defaults to the id if None. 133 """ 134 ... 135 136 @name.setter 137 def name(self, name: str) -> None: 138 """Setter for name""" 139 ... 140 141 @property 142 def metadata(self) -> Dict[str, Any]: 143 """ 144 Optional extensible key-value metadata. 145 """ 146 ...
The interface contract for a domain entity to participate in a presence assertion.
Each entity requires only a unique identifier, a user-facing name.
Optional metadata may be provided and exposes specific attributes of the domain entities that are relevant when interpreting or manipulating the results of an analysis using the machinery of the calculus.
See Entity
for an example of a concrete implementation.
120 @property 121 def id(self) -> str: 122 """ 123 A stable, unique identifier for the entity. 124 Used for indexing and identity. 125 Defaults to a uuid.uuid4(). 126 """ 127 ...
A stable, unique identifier for the entity. Used for indexing and identity. Defaults to a uuid.uuid4().
149class EntityMixin: 150 """A mixin class that can be used to inject common shared behavior 151 of objects satisfying the EntityProtocol into existing domain entities. 152 """ 153 154 def summary(self: EntityProtocol) -> str: 155 """ 156 Return a human-readable summary based on id and metadata. 157 """ 158 meta = getattr(self, "metadata", {}) 159 if not meta: 160 return f"Element[{self.id}] name = {self.name} (no metadata)" 161 formatted = ", ".join(f"{k}={v!r}" for k, v in meta.items()) 162 return f"Element[{self.id}] name = {self.name} {{{formatted}}}"
A mixin class that can be used to inject common shared behavior of objects satisfying the EntityProtocol into existing domain entities.
154 def summary(self: EntityProtocol) -> str: 155 """ 156 Return a human-readable summary based on id and metadata. 157 """ 158 meta = getattr(self, "metadata", {}) 159 if not meta: 160 return f"Element[{self.id}] name = {self.name} (no metadata)" 161 formatted = ", ".join(f"{k}={v!r}" for k, v in meta.items()) 162 return f"Element[{self.id}] name = {self.name} {{{formatted}}}"
Return a human-readable summary based on id and metadata.
165class EntityView(EntityMixin): 166 """ 167 A view class that allows domain objects to behave like entities 168 169 ```python 170 class Customer: 171 def __init__(self, id, name, segment): 172 self._id = id 173 self._name = name 174 self._segment = segment 175 176 @property 177 def id(self): 178 return self._id 179 180 @property 181 def name(self): 182 return self._name 183 184 @property 185 def metadata(self): 186 return {"segment": self._segment} 187 ``` 188 189 You can wrap it with `EntityView` to use it with presence-related infrastructure: 190 191 ```python 192 customer = Customer("cust-001", "Alice Chen", "Enterprise") 193 entity = EntityView(customer) 194 195 print(entity.id) # cust-001 196 print(entity.name) # Alice Chen 197 print(entity.metadata) # {'segment': 'Enterprise'} 198 print(entity.summary()) # Element[cust-001] name = Alice Chen {segment='Enterprise'} 199 ``` 200 """ 201 202 __slots__ = ("_id", "_name", "_metadata") 203 204 def __init__(self, base: EntityProtocol): 205 self._base = base 206 207 @property 208 def id(self) -> str: 209 return self._base.id 210 211 @property 212 def name(self) -> Optional[str]: 213 return self._base.name 214 215 @property 216 def metadata(self) -> Dict[str, Any]: 217 return self._base.metadata
A view class that allows domain objects to behave like entities
class Customer:
def __init__(self, id, name, segment):
self._id = id
self._name = name
self._segment = segment
@property
def id(self):
return self._id
@property
def name(self):
return self._name
@property
def metadata(self):
return {"segment": self._segment}
You can wrap it with EntityView
to use it with presence-related infrastructure:
customer = Customer("cust-001", "Alice Chen", "Enterprise")
entity = EntityView(customer)
print(entity.id) # cust-001
print(entity.name) # Alice Chen
print(entity.metadata) # {'segment': 'Enterprise'}
print(entity.summary()) # Element[cust-001] name = Alice Chen {segment='Enterprise'}
Inherited Members
220class Entity(EntityMixin, EntityProtocol): 221 """A default implementation of fully functional entity. 222 223 ```python 224 elements = [ 225 Entity(id="cust-001", name="Alice Chen", metadata={"type": "customer"}), 226 Entity(id="cust-002", name="Bob Gupta", metadata={"type": "customer"}), 227 Entity(id="pros-003", name="Dana Rivera", metadata={"type": "prospect"}), 228 ] 229 boundaries = [ 230 Entity(id="seg-enterprise", name="Enterprise Segment", metadata={"region": "NA"}), 231 Entity(id="seg-smb", name="SMB Segment", metadata={"region": "EMEA"}), 232 Entity(id="seg-dormant", name="Flight Risk", metadata={"status": "inactive", "last login": "2024-06-01"}), 233 Entity(id="seg-prospect", name="Active Prospects", metadata={"status": "demo given"}) 234 ] 235 236 for e in elements: 237 print(f"Element: {e.summary()}") 238 239 for b in boundaries: 240 print(f"Boundary: {b.summary()}") 241 ``` 242 """ 243 __slots__ = ("_id", "_name", "_metadata") 244 245 # noinspection PyProtocol 246 def __init__(self, id: Optional[str] = None, name: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None): 247 self._id: str = id or str(uuid.uuid4()) 248 self._name: str = name or self.id 249 self._metadata: Dict[str, Any] = metadata or {} 250 251 @property 252 def id(self) -> str: 253 return self._id 254 255 # noinspection PyProtocol 256 @property 257 def name(self) -> str: 258 return self._name 259 260 @name.setter 261 def name(self, name: str) -> None: 262 self._name = name 263 264 @property 265 def metadata(self) -> Dict[str, Any]: 266 return self._metadata 267 268 def __str__(self) -> str: 269 return self.summary()
A default implementation of fully functional entity.
elements = [
Entity(id="cust-001", name="Alice Chen", metadata={"type": "customer"}),
Entity(id="cust-002", name="Bob Gupta", metadata={"type": "customer"}),
Entity(id="pros-003", name="Dana Rivera", metadata={"type": "prospect"}),
]
boundaries = [
Entity(id="seg-enterprise", name="Enterprise Segment", metadata={"region": "NA"}),
Entity(id="seg-smb", name="SMB Segment", metadata={"region": "EMEA"}),
Entity(id="seg-dormant", name="Flight Risk", metadata={"status": "inactive", "last login": "2024-06-01"}),
Entity(id="seg-prospect", name="Active Prospects", metadata={"status": "demo given"})
]
for e in elements:
print(f"Element: {e.summary()}")
for b in boundaries:
print(f"Boundary: {b.summary()}")
A stable, unique identifier for the entity. Used for indexing and identity. Defaults to a uuid.uuid4().