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()
@runtime_checkable
class EntityProtocol(typing.Protocol):
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.

id: str
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().

name: str
129    @property
130    def name(self) -> str:
131        """
132        A user facing name for the entity, defaults to the id if None.
133        """
134        ...

A user facing name for the entity, defaults to the id if None.

metadata: Dict[str, Any]
141    @property
142    def metadata(self) -> Dict[str, Any]:
143        """
144        Optional extensible key-value metadata.
145        """
146        ...

Optional extensible key-value metadata.

class EntityMixin:
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.

def summary(self: EntityProtocol) -> str:
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.

class EntityView(EntityMixin):
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'}
EntityView(base: EntityProtocol)
204    def __init__(self, base: EntityProtocol):
205        self._base = base
id: str
207    @property
208    def id(self) -> str:
209        return self._base.id
name: Optional[str]
211    @property
212    def name(self) -> Optional[str]:
213        return self._base.name
metadata: Dict[str, Any]
215    @property
216    def metadata(self) -> Dict[str, Any]:
217        return self._base.metadata
Inherited Members
EntityMixin
summary
class Entity(EntityMixin, EntityProtocol):
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()}")
Entity( id: Optional[str] = None, name: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None)
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 {}
id: str
251    @property
252    def id(self) -> str:
253        return self._id

A stable, unique identifier for the entity. Used for indexing and identity. Defaults to a uuid.uuid4().

name: str
256    @property
257    def name(self) -> str:
258        return self._name

A user facing name for the entity, defaults to the id if None.

metadata: Dict[str, Any]
264    @property
265    def metadata(self) -> Dict[str, Any]:
266        return self._metadata

Optional extensible key-value metadata.

Inherited Members
EntityMixin
summary