Understanding perpetual and time-variant properties

A property in LUSID can be either perpetual or time-variant. The lifetime is determined by the property type.

Note the following:

  • The concept of lifetime refers to validity rather than mutability; any property value can be corrected at any time. Rather, a perpetual property represents information expected to be valid forever, for example a person's place of birth. A time-variant property represents information expected to change over time, for example a person's address. More on LUSID and bitemporality.

  • While entities of most types support perpetual properties, not all support time-variant properties.

  • Either can be single or multi-value.

Perpetual properties

A perpetual property has a value that is considered to be permanent in LUSID, for example a person's place of birth.

When you add a perpetual property value, it is applied with the current AsAt date and is effective from the beginning of time until the end of time. For example, to set John Smith’s place of birth, you might make the following request to the SetPersonProperties API:

URL: POST /api/persons/corporate/username/john_smith/properties
Request body:

{ 
  "properties": { 
    "person/info/placeOfBirth": [ 
      { 
        "key": "Person/info/placeOfBirth", 
        "value": { 
          "labelValue": "London" 
        } 
      } 
    ] 
  } 
}

The response might be as follows:

{ 
  "displayName": "JohnSmith", 
  "identifiers": { 
    "person/corporate/username": { 
      "key": "Person/corporate/username", 
      "value": { 
        "labelValue": "john_smith" 
      } 
    } 
  }, 
  "properties": { 
    "person/info/placeOfBirth": [ 
      { 
        "key": "Person/info/placeOfBirth", 
        "value": { 
          "labelValue": "London" 
        }, 
        "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00", 
        "effectiveUntil": "9999-12-31T23:59:59.9999999+00:00" 
      } 
    ] 
   }, 
  "version": { 
    "effectiveFrom": "2021-08-03T00:00:00.0000000+00:00", 
    "asAtDate": "1"  // Integer value used for illustrative purposes; the actual system time uses the 0000-00-00T00:00:00.0000000+00:00 format. 
  } 
}

We can represent this visually as follows:

While the property is considered perpetual, it is of course possible to enter the wrong information. LUSID allows you to correct bad data by overwriting any perpetual property value. A full audit history is maintained, with past values available by requesting the property with an AsAt datetime prior to the correction. The new value is stored with the AsAt datetime of the correction, which we could represent visually as follows:

Time-variant properties

A time-variant property has a value that is considered likely to change in LUSID over time, for example a person's address. A time-variant property is therefore associated with an effective date range.

The effective date range is described by effectiveFrom and effectiveUntil datetimes. If no effectiveUntil date is provided, the value will be applied until the effectiveFrom date of the next value in the timeline (which is the end of time if there are no subsequent values).

Consider the following sequence of events. In the first request, we'll set John Smith's address to be 1 Main Street, with an effectiveFrom date of 1 January 2019 until the end of time.

First request: AsAt=1
URL: POST /api/persons/corporate/username/john_smith/properties
Request body:

{ 
  "properties": { 
    "person/info/address": [ 
      { 
        "key": "Person/info/address", 
        "value": { 
          "labelValue": "1 Main Street" 
        }, 
        "effectiveFrom": "2019-01-01T00:00:00.00Z" 
      } 
    ] 
  } 
}

In the second request, we'll update John Smith's address to be 2 High Street, with an effectiveFrom date of 1 January 2020 until the end of time. The previous value of 1 Main Street is automatically updated so that it is now effectiveUntil the 1 January 2020.

Second request: AsAt=2
URL: POST /api/persons/corporate/username/john_smith/properties
Request body:

{ 
  "properties": { 
    "person/info/address": [ 
      { 
        "key": "Person/info/address", 
        "value": { 
          "labelValue": "2 High Street" 
        }, 
        "effectiveFrom": "2020-01-01T00:00:00.00Z" 
      } 
    ] 
  } 
}

We subsequently learn that instead of moving to 2 High Street on 1 January 2020, John Smith actually moved to 3 Station Road, staying there for one year. In the third request, we'll change the property value to 3 Station Road with an effectiveFrom date of 1 January 2020, and specify an explicit effectiveUntil date of 1 January 2021. This is now the 'second value'. The previous second value of 2 High Street becomes the 'third value'; its effectiveFrom date is automatically updated to 1 January 2021.

Second request: AsAt=3
URL: POST /api/persons/corporate/username/john_smith/properties
Request body:

{ 
  "properties": { 
    "person/info/address": [ 
      { 
        "key": "Person/info/address", 
        "value": { 
          "labelValue": "3 Station Road" 
        }, 
        "effectiveFrom": "2020-01-01T00:00:00.00Z" 
        "effectiveUntil": "2021-01-01T00:00:00.00Z" 
      } 
    ] 
  } 
}

We can represent this visually as follows:

Retrieving property values as a time-series

You can specify EffectiveAt and AsAt datetimes when retrieving properties for an entity, but it can be difficult and time-consuming to try to understand the full extent of historic changes.

Certain types of entity therefore have property time series API endpoints that return the full list of values for a particular property. For example, for a Person entity you could use the GetPersonPropertyTimeSeries API, specifying the person's identifier in the URL and the property key as a query string parameter.

If we retrieve John Smith's time-variant address property, for example, we can see that values are returned firstly in EffectiveAt datetime order, and subsequently in AsAt datetime order if they cover the same period. Values that are no longer valid at the AsAt datetime of the request have the status superseded, and current values have the status prevailing:

{ 
  "values": [ 
    { 
      "value": { 
        "labelValue": "1 Main Street" 
      }, 
      "effectiveRange": { 
        "fromDate": "2019-01-01T00:00:00.0000000+00:00", 
        "untilDate": "9999-12-31T23:59:59.9999999+00:00" 
      }, 
      "asAtRange": { 
        "fromDate": "1", 
        "untilDate": "2" 
      }, 
      "status": "superseded" 
    }, 
    { 
      "value": { 
        "labelValue": "1 Main Street" 
      }, 
      "effectiveRange": { 
        "fromDate": "2019-01-01T00:00:00.0000000+00:00", 
        "untilDate": "2020-01-01T00:00:00.0000000+00:00" 
      }, 
      "asAtRange": { 
        "fromDate": "2" 
      }, 
      "status": "prevailing" 
    }, 
    { 
      "value": { 
        "labelValue": "2 High Street" 
      }, 
      "effectiveRange": { 
        "fromDate": "2020-01-01T00:00:00.0000000+00:00", 
        "untilDate": "9999-12-31T23:59:59.9999999+00:00" 
      }, 
      "asAtRange": { 
        "fromDate": "2", 
        "untilDate": "3" 
      }, 
      "status": "superseded" 
    }, 
    { 
      "value": { 
        "labelValue": "3 Station Rd" 
      }, 
      "effectiveRange": { 
        "fromDate": "2020-01-01T00:00:00.0000000+00:00", 
        "untilDate": "2021-01-01T00:00:00.0000000+00:00" 
      }, 
      "asAtRange": { 
        "fromDate": "3" 
      }, 
      "status": "prevailing" 
    }, 
    { 
      "value": { 
        "labelValue": "2 High Street" 
      }, 
      "effectiveRange": { 
        "fromDate": "2021-01-01T00:00:00.0000000+00:00", 
        "untilDate": "9999-12-31T23:59:59.9999999+00:00" 
      }, 
      "asAtRange": { 
        "fromDate": "3" 
      }, 
      "status": "prevailing" 
    } 
  ] 
}

To only retrieve prevailing values, set the API endpoint's filter parameter, for example status eq 'prevailing'.