Loading a Design#
Rangekeeper provides API access to Speckle. This enables loading and extending a 3D design with Rangekeeper in order to produce its financial valuation, as well as execute any automated decision-making or optimisation processes, and sending the results back to Speckle.
Object Model#
Before walking through the I/O methods, it is important to understand the
requirements Rangekeeper places on how a design must be structured,
so that the Flow
s and Stream
s of a DCF Proforma can be appropriately
attributed to the design’s objects (especially its representations of
physical spaces, components, and equipment).
Entities and Relationships#
A holistic representation of a real estate asset is that it is a web of inter-related ‘entities’; where each entity is a ‘thing’ and can have multiple relationships with other entities. For example, a floor is a type of entity (say, a “Space”) and it has relationships with floors above and below it, as well as relationships to its sub-entities (e.g. rooms), or even what services it (e.g. the mechanical systems controlling its air conditioning)
Entity
s#
An Entity
is a ‘thing’ that has substance, or is composed of sub-Entity
s
that have substance, material, mass, volume, existence, are ‘real’, etc.
Relationship
s#
Relationship
s describe some sort of (directed) association, connection, or
link between Entity
s. Each Relationship
is described by its ‘type’; for
instance, an Entity
can be related to another by virtue of it “being
contained (spatially)” by the other, or it could be “installed before” another
Entity
.
There can be any number of Relationship
s between any number of Entity
s.
Assembly
s#
In order to clearly encapsulate ‘groups’ of intuitively-related Entity
s,
Rangekeeper has a concept of an Assembly
, which is an object that defines
a collection of Relationship
s and their associated Entity
s.
Assembly
s are Entity
s themselves, too –- so they can also be related to
other Entity
s.
This means that Rangekeeper can traverse from Assembly
to Assembly
through
overlapping Entity
s, in a similar fashion to common-sense conceptualisation
of how a real estate asset is structured.
Definitions#
Properties of Entity
s#
All Entity
s have the following properties:
entityId: a unique & immutable identifier for the
Entity
over the whole of its lifetime.Name: human-readable text identifying the
Entity
Type: an
entityType
(which is a node of a tree ofentityType
s)Attributes: a key-value store of specific properties of the
Entity
(eg, its area, geometry, material, cash flows, etc).
In addition, Assembly
s record a set of Relationship
s about Entity
s that
are related to, or in, the Assembly
.
Relationships: a set of
Relationship
s
Properties of Relationship
s#
Meanwhile, all Relationship
s have the following properties:
Type: a
RelationshipType
, which is an item from a list of definedRelationshipType
sSource: the
Entity
that is the source of theRelationship
Target: the
Entity
that is the target of theRelationship
Event
s#
Every Entity
can exist in both space and time. Most Entity
s will have (or
its sub-Entity
s will have, in the case of an Assembly
) geometry/s that
define the extent of space that the entity exists in.
To represent how an Entity
exists in time, the Entity
has Event
s
recorded against it. For example, an Event
may record the installation of the
entity
during construction, or it may record production of revenue during
operation of the real estate project as an asset.
Flow
s and Stream
s are subclasses of Event
s.
Rationale and Implications#
Rangekeeper will use the Relationship
s defined in the Assembly
/s of a
design to structure the “drill-downs” and “roll-ups” (ie, the
compositions) of Flow
s into Stream
s that are appropriate for the kinds of
summations and metrics that the DCF Proforma model requires.
For example, an office building may produce revenue from multiple tenants. A
tenant may occupy multiple floors, or a part (space) of a single floor.
For Rangekeeper, each space would be an entity
; with its Events
property
containing a set of Flow
s and/or Stream
s that represent revenues or costs
associated with that space. Those may be aggregated into cash flows generated by
the tenant of those spaces, and analysed as such, or they may be aggregated per
floor, or per building, or per project.
This makes it simple to calculate things like the share of total revenue generated by each tenant; or the share of revenue generated by each floor. Likewise with costs.
The multi-faceted nature of the Entity
-Relationship
model enables
multiple pathways for those aggregations to be specified, and thus enables an
efficient and broad ability to query the model for the kinds of analyses
pertinent to real estate valuation.
Technicalities#
To provide a Rangekeeper model with the Entity
-Relationship
organization,
those need to either be defined in the design itself, or they need to
be specified after receiving and loading the design.
Defining Entity
s and Relationship
s in the Design Scenario#
Rangekeeper uses the Speckle service and data model
for the exchange of designs, and scaffolds entity
s off a Speckle
Base
It also provides two means to define entity
s and Relationship
s:
A plugin to [McNeel’s Rhinoceros3D Grasshopper] (https://www.rhino3d.com/6/new/grasshopper/), that enables both automated and manual assignment of
Relationship
s to geometries before their export to Speckle; andManual assignment of
Relationship
s toEntity
s once they have been loaded from a Speckle stream in Rangekeeper.
Example Design#
An example property has been documented and uploaded to Speckle for us to use as a demonstration of the object model and its use in Rangekeeper.
This design is composed of two mixed-use buildings, that both share a common basement plinth, used for parking and shared mechanical and other services (e.g. garbage disposal, storage)
To access the design in Rangekeeper, we need to load it from Speckle. First load neccesary libraries:
import os
from IPython.display import IFrame
import rangekeeper as rk
Authenticate and Load the Design from Speckle#
# Authenticate with Speckle
speckle = rk.api.Speckle(
host="speckle.xyz",
token=os.getenv('SPECKLE_TOKEN')) # Note if you run this notebook yourself, you'll need to set this environment variable
SpeckleClient( server: https://speckle.xyz, authenticated: True )
The design is stored in a Speckle ‘stream’, which is a container for a set of ‘commits’, or versions of the data.
stream_id = "c0f66c35e3"
commit_id = speckle.get_latest_commit_id(stream_id)
# Load the design data
data = speckle.get_commit(stream_id=stream_id) # Providing no commit_id defaults to the latest commit
data
Base(id: e7acaac21ae7e9369339900a4aaeb827, speckle_type: Base, totalChildrenCount: 3006)
Inspect the Design#
IFrame("https://speckle.xyz/embed?stream={0}&commit={1}".format(stream_id, commit_id), width='100%', height=800)
# Let's identify the root members of the:
roots = data.get_dynamic_member_names()
roots
['@property', '@context']
In this design, there are two members (Speckle Objects) at the root of the model:
‘@context’, and
‘@property’
Note
Speckle Objects whose member names begin with an @
indicate ‘detached’ data:
See The Base Object: Detaching
for more information.
For future reference, if we wanted to see their spatial representation, we can
pass them in to the Speckle Viewer’s “objects” endpoint with their id
s:
IFrame("https://speckle.xyz/streams/{0}/objects/{1}".format(stream_id, data['@property']['id']), width='100%', height=800)
We can even use the Speckle Viewer’s ‘filter’ functionality to see only the certain types of objects contained within the spaces of the design (try toggling the isolation mode for “floor” or “utilities” types):
label = 'type'
IFrame('https://speckle.xyz/streams/{0}/commits/{1}?filter=%7B%22propertyInfoKey%22%3A%22{2}%22%7D'.format(
stream_id,
commit_id,
label), width='100%', height=800)
Rangekeeper Entity
s in the Design#
Of course, not all Speckle Objects are Rangekeeper Entity
s for us to use in
our valuations and modeling.
To make parsing the design easier, Rangekeeper provides some helper
methods to convert the Speckle Objects into Rangekeeper Entity
s:
# Return any Speckle Objects in the '@design' trunk that have 'entityId's:
parsed = rk.api.Speckle.parse(base=data['@property'])
# (Recursively) Convert the Speckle Objects into Rangekeeper Entities:
property = rk.api.Speckle.to_rk(
bases=list(parsed.values()),
name='property',
type='archetype')
# (We can check that it is an Assembly:)
isinstance(property, rk.graph.Assembly)
Warning: Duplicate Entity 6496e279-88ca-49f4-b48c-6c0f156e0a58 [utilities] found.
Existing Entity is an Assembly while new Entity is not. Keeping Assembly.
Warning: Duplicate Entity 6496e279-88ca-49f4-b48c-6c0f156e0a58 [utilities] found.
Existing Entity is an Assembly while new Entity is not. Keeping Assembly.
Warning: Duplicate Entity 6496e279-88ca-49f4-b48c-6c0f156e0a58 [utilities] found.
Existing Entity is an Assembly while new Entity is not. Keeping Assembly.
Warning: Duplicate Entity 2b51847e-ec54-415d-afa0-ce32d74c6144 [plinthparking] found.
Existing Entity is an Assembly while new Entity is not. Keeping Assembly.
True
Querying the Design’s Object Graph#
Rangekeeper uses the NetworkX library to represent
the object graph of the design, where each Assembly
is a MultiDiGraph
of
Entity
nodes and Relationship
edges.
This means we can query it pythonically:
# Get the "BuildingA" Assembly:
buildingA = [entity for (entityId, entity) in property.get_entities().items() if entity['name'] == 'buildingA'][0]
print('buildingA: {0}'.format(buildingA))
buildingA: Assembly: buildingA
Type: building
Members: ['type', 'name']
Entities: [('5ebb9736-5f18-4103-a5a4-040cd2e9b019', {'entity': Assembly: buildingA (Type: building)}), ('c0348c0f-a31f-421b-b6bc-0fe0d5ea947f', {'entity': Assembly: buildingAhotel (Type: space)}), ('1d0841bf-aa28-41d7-ba75-0ad0f3d57d21', {'entity': Assembly: buildingAretail (Type: space)}), ('7716632b-f43c-4ed2-9f84-26709361bbfe', {'entity': Assembly: buildingAresidential (Type: space)}), ('01aec121-ef19-4e6d-9f09-b2ce007b717d', {'entity': Assembly: buildingAretail (Type: space)}), ('6f6b227b-b28b-4412-89bd-0a9bbc47164e', {'entity': Assembly: buildingAparking (Type: space)}), ('7eec5208-fc0e-4dfd-bd0b-b1d226b2e97e', {'entity': Entity: buildingAcores (Type: utilities)})]
Relationships: [('5ebb9736-5f18-4103-a5a4-040cd2e9b019', 'c0348c0f-a31f-421b-b6bc-0fe0d5ea947f', 'spatiallyContains'), ('5ebb9736-5f18-4103-a5a4-040cd2e9b019', '1d0841bf-aa28-41d7-ba75-0ad0f3d57d21', 'spatiallyContains'), ('5ebb9736-5f18-4103-a5a4-040cd2e9b019', '7716632b-f43c-4ed2-9f84-26709361bbfe', 'spatiallyContains'), ('5ebb9736-5f18-4103-a5a4-040cd2e9b019', '01aec121-ef19-4e6d-9f09-b2ce007b717d', 'spatiallyContains'), ('5ebb9736-5f18-4103-a5a4-040cd2e9b019', '6f6b227b-b28b-4412-89bd-0a9bbc47164e', 'spatiallyContains'), ('5ebb9736-5f18-4103-a5a4-040cd2e9b019', '7eec5208-fc0e-4dfd-bd0b-b1d226b2e97e', 'spatiallyContains')]
Because we can also define directed, labelled Relationship
s between Entity
s,
we can identify relatives of Entity
s accordingly:
# Get all relatives of BuildingA where BuildingA is the source of
# a 'spatiallyContains' relationship:
buildingA_containment = buildingA.get_relatives(
outgoing=True,
relationship_type='spatiallyContains',
assembly=property)
print('buildingA Containment: \n {0}\n'.format(buildingA_containment))
buildingA Containment:
[Assembly: buildingAhotel (Type: space), Assembly: buildingAretail (Type: space), Assembly: buildingAresidential (Type: space), Assembly: buildingAretail (Type: space), Assembly: buildingAparking (Type: space), Entity: buildingAcores (Type: utilities)]
buildingAresi = [entity for entity in buildingA_containment if entity['name'] == 'buildingAresidential'][0]
print('buildingAresidential: {0}'.format(buildingAresi))
buildingAresidential: Assembly: buildingAresidential
Type: space
Members: ['type', 'volume', 'use', '@displayValue', 'name', 'renderMaterial']
Entities: [('7716632b-f43c-4ed2-9f84-26709361bbfe', {'entity': Assembly: buildingAresidential (Type: space)}), ('bf5a4444-6abd-4b89-b7a8-d758df5134fe', {'entity': Entity: buildingAresidentialFloor0 (Type: floor)}), ('dbf25b07-6f85-45bb-905c-de5b8562f5e7', {'entity': Entity: buildingAresidentialFloor1 (Type: floor)}), ('507e4ef2-fe13-44d7-af5f-bcf717b3de53', {'entity': Entity: buildingAresidentialFloor2 (Type: floor)}), ('bb815059-7516-45df-8fac-16f0e62242f3', {'entity': Entity: buildingAresidentialFloor3 (Type: floor)})]
Relationships: [('7716632b-f43c-4ed2-9f84-26709361bbfe', 'bf5a4444-6abd-4b89-b7a8-d758df5134fe', 'spatiallyContains'), ('7716632b-f43c-4ed2-9f84-26709361bbfe', 'dbf25b07-6f85-45bb-905c-de5b8562f5e7', 'spatiallyContains'), ('7716632b-f43c-4ed2-9f84-26709361bbfe', '507e4ef2-fe13-44d7-af5f-bcf717b3de53', 'spatiallyContains'), ('7716632b-f43c-4ed2-9f84-26709361bbfe', 'bb815059-7516-45df-8fac-16f0e62242f3', 'spatiallyContains')]
We can also visualize the design’s graph as well:
property.plot(name='assets/property')
IFrame(src="./property.html", width='100%', height=800)
assets/property.html