Skip to content

Leveraging the TravelTime Extension for Reproducibility and Data Lineage

STA API time-travel extension

This extension assists istSTA users in accessing historical time travel data. It enables users to retrieve information from a web service as it appeared at a specific moment in time, using a new query parameter called as_of.

Additionally, it introduces a new entity called Commit, which enables data lineage, allowing users to trace data changes. From a scientific perspective, this extension enables FAIR data management by allowing datasets to be permanently cited. This is achieved by using a combination of the service address (in red), the request that returns the dataset (in green), and the dataset's status at a specific time instant (in orange) as a Persistent Identifier for reference.

Example: https://<base_url>/<version>/<entity>?$expand=<entity>&$as_of=<date_time>

Definition

The time-travel extension adds the following optional query parameters to any STA request:

Parameter Type Description
as_of ISO 8601 date-time a date-time parameter to specify the exact moment for which the data is requested
from_to ISO 8601 period a period parameter to specify the time interval for which the data is requested

The time-travel extension introduces a new entity, Commit, with the following properties:

Properties Type Multiplicity and use Description
author string(128) One (mandatory) Authority, Username or User Profile Link
encodingType string One (optional) The encoding type of the message (default is text).
message string(256) One (mandatory) Commit message detailing the scope, motivation, and method of the transaction.
date ISO 8601 date-time One (mandatory) A date-time that specifies the exact moment when the commit was executed.

Commits are related to SensorThings API entities with a one-to-zero-or-one (1:0..1) relationship.

Preliminary Steps

This section contains the preliminary steps to set up the base URL and import necessary libraries.

Replace IST_SOS_ENDPOINT in the following script with your istSOS base URL (http://localhost:8018/istsos4/v1.1 or https://istsos.org/v4/v1.1).

import json
import re
from datetime import datetime, timedelta
import pytz

import requests
from IPython.display import Markdown, display

IST_SOS_ENDPOINT = "http://localhost:8018/istsos4/v1.1"

Login as editor

username = input("Enter your username: ")
password = input("Enter your password: ")

if not username or not password:
    print("Username or password is empty")

else:
    data = {
        "username": username,
        "password": password,
        "grant_type": "password",
    }

    response = requests.post(IST_SOS_ENDPOINT + "/Login", data=data)
    if response.status_code == 200:
        token = response.json()["access_token"]
        print(
            f"Token expires at: { datetime.fromtimestamp(response.json()['expires_in'])}"
        )
        prefix = username + "-"
        print("Your station name will be prefixed with: " + prefix)
    else:
        result = json.dumps(response.json(), indent=2)
        display(Markdown(f"```json\n{result}\n```"))

Create a Thing

body = {
    "name": f"{prefix}Lugano Lakee",
    "description": "The Apline Lake located in Southern Switzerland",
    "properties": {
        "Max depth": "288 m",
    },
}

response = requests.post(
    IST_SOS_ENDPOINT + "/Things",
    data=json.dumps(body),
    headers={
        "Content-type": "application/json",
        "Authorization": f"Bearer {token}",
        "Commit-message": "Create new thing",
    },
)

if response.status_code == 201:
    print(f"Thing created successfully ({response.headers['location']})")
    match = re.search(r"\((\d+)\)", response.headers["location"])
    if match:
        thing_id = int(match.group(1))
    else:
        print("No number found in parentheses.")
else:
    result = json.dumps(response.json(), indent=2)
    display(Markdown(f"```json\n{result}\n```"))

Take a rest and whait e few minutes... ;-)

Update the Thing

body = {
    "properties": {
        "Catchment area": "565.6 km²",
        "Surface Area": "38.7 km²",
        "Avg. Depth": "124 m",
        "Max depth": "288 m",
        "Water Volume": "6.5 km³",
        "Surface elevation": "271 m",
        "Primary inflows": [
            "Vedeggio",
            "Cassarate",
            "Cuccio",
            "Laveggio",
            "Magliasina",
            "Bolletta",
            "Scairolo",
        ],
        "Primary outflows": "Tresa",
    },
}

response = requests.patch(
    f"{IST_SOS_ENDPOINT}/Things({thing_id})",
    data=json.dumps(body),
    headers={
        "Content-type": "application/json",
        "Authorization": f"Bearer {token}",
        "Commit-message": "Corrected properties",
    },
)

if response.status_code == 200:
    print(f"Thing properties updated successfully")
    datetime_before_update = datetime.now(pytz.utc) - timedelta(seconds=1)
    datetime_before_update = datetime_before_update.replace(tzinfo=None)
    datetime_before_update = datetime_before_update.isoformat() + "Z"
else:
    result = json.dumps(response.json(), indent=2)
    display(Markdown(f"```json\n{result}\n```"))

Retrieve the Thing

Retrieve the current state of the Thing

response = requests.get(
    f"{IST_SOS_ENDPOINT}/Things({thing_id})?$expand=Commit",
    headers={
        "Authorization": f"Bearer {token}",
    },
)
result = json.dumps(response.json(), indent=2)
display(Markdown(f"```json\n{result}\n```"))

Retrieve the Thing's state at a specific point in time (before update)

response = requests.get(
    f"{IST_SOS_ENDPOINT}/Things({thing_id})?$expand=Commit&amp;$as_of={datetime_before_update}",
    headers={
        "Authorization": f"Bearer {token}",
    },
)
result = json.dumps(response.json(), indent=2)
display(Markdown(f"```json\n{result}\n```"))

Retrieve the historical states of the Thing

datetime_now = datetime.now(pytz.utc) - timedelta(seconds=1)
datetime_now = datetime_now.replace(tzinfo=None)
datetime_now = datetime_now.isoformat() + "Z"
response = requests.get(
    f"{IST_SOS_ENDPOINT}/Things({thing_id})?$expand=Commit&amp;$from_to={datetime_before_update}/{datetime_now}",
    headers={
        "Authorization": f"Bearer {token}",
    },
)
result = json.dumps(response.json(), indent=2)
display(Markdown(f"```json\n{result}\n```"))