Context items¶
Context items are the annotations that live in TrendMiner's ContextHub: each one
marks a period of time on a specific Tag,
Asset or
Attribute, carries a
ContextType, and can hold a
description, keywords and typed fields.
The SDK represents a set of context items as events — a
pandas.DataFrame
whose IntervalIndex
holds each item's start and end, alongside columns for its type, component and other
metadata (see Interval utilities for this shared
events vocabulary). Every operation in this notebook is really just building or
reshaping such a DataFrame and handing it to the
client.context facade.
This example shows how to create, retrieve, update and delete context items, and then how to populate them at scale — from a value-based search, from a monitor, and from a scheduled update service.
Setup¶
We start by authenticating a client.
import os
import pandas as pd
from dotenv import find_dotenv, load_dotenv
from trendminer_interface import client_from_credentials
# Credentials and server details are securely stored in .env.docs for our example code.
# find_dotenv walks up from the working directory to locate it, so this works
# regardless of the directory the notebook is run from.
load_dotenv(find_dotenv(".env.docs", usecwd=True))
client = client_from_credentials(
url=os.environ["TM_URL"],
client_id=os.environ["TM_CLIENT_ID"],
client_secret=os.environ["TM_CLIENT_SECRET"],
)
Creating context items¶
Creating context items comes down to assembling a DataFrame with the right structure
and passing it to
client.context.item.create. We build
that DataFrame up one column at a time.
The index must be an
IntervalIndex
of timezone-aware timestamps — each interval becomes one context item's start and end.
Here we use
pandas.interval_range
to make one month-long item for each of the last six months, anchored to the client's
timezone (client.tz).
# 1 item per month for the last 6 months
df = pd.DataFrame(
index=pd.interval_range(
end=pd.Timestamp.now(tz=client.tz).normalize(),
freq="MS",
periods=6,
),
)
The type column holds each item's
ContextType, which we look up by name
through the client.context.type
interface. We assign a single type to every row here, but the column may mix types
freely.
context_type = client.context.type.from_name("Month Example")
df["type"] = context_type
Every item is anchored to a component — a
Tag,
Asset or
Attribute — supplied in the component
column. We look our tag up by name with
client.tag.from_name; see
Asset Framework navigation for retrieving assets and
attributes instead.
month_tag = client.tag.from_name("TM_Month_UTC")
df["component"] = month_tag
Two further columns are recognised as first-class item metadata: description (a
string) and keywords (a list of strings per row). Both are optional.
description = "Monthly overview"
df["description"] = description
keywords = ["sdk"]
df["keywords"] = [keywords] * len(df)
df["month"] = df.index.left.month_name()
The completed DataFrame looks like this:
df
| type | component | description | keywords | month | |
|---|---|---|---|---|---|
| (2026-03-01 00:00:00+00:00, 2026-04-01 00:00:00+00:00] | month_example | TM_month_UTC | Monthly overview | [sdk] | March |
| (2026-04-01 00:00:00+00:00, 2026-05-01 00:00:00+00:00] | month_example | TM_month_UTC | Monthly overview | [sdk] | April |
| (2026-05-01 00:00:00+00:00, 2026-06-01 00:00:00+00:00] | month_example | TM_month_UTC | Monthly overview | [sdk] | May |
| (2026-06-01 00:00:00+00:00, 2026-07-01 00:00:00+00:00] | month_example | TM_month_UTC | Monthly overview | [sdk] | June |
| (2026-07-01 00:00:00+00:00, 2026-08-01 00:00:00+00:00] | month_example | TM_month_UTC | Monthly overview | [sdk] | July |
| (2026-08-01 00:00:00+00:00, 2026-09-01 00:00:00+00:00] | month_example | TM_month_UTC | Monthly overview | [sdk] | August |
With the DataFrame complete, a single call creates every item on the server:
client.context.item.create(items=df)
Retrieving, updating and deleting context items¶
Items are retrieved by describing which items you want with a set of filters.
Each filter is built from the
client.context.filter interface;
combined, they narrow the result down to the items created above. Filtering on
created_by is the only way
to fully control what comes back — other users may have created items that match the
rest of your filters.
The filters are bundled into a
ContextHubViewDefinition
via client.context.view.define, whose
get_items returns a lazy
PagedDataFrame; calling collect
materialises it into an events DataFrame. Notice that creating the items added a lot
of metadata (keys, identifiers, timestamps), while the data we supplied came back
intact.
context_filters = [
client.context.filter.created_by.new(users=[client.user.self]),
client.context.filter.period.new(period=pd.Timedelta(days=200)),
client.context.filter.context_types.new(types=[context_type]),
client.context.filter.keyword.new(keywords=["sdk"]),
client.context.filter.components.new(queries=[(month_tag, "self")])
]
chv_def = client.context.view.define(
filters=context_filters,
)
df = chv_def.get_items().collect()
df
| open | key | identifier | identifier_external | description | type | component | keywords | created_by | created_at | last_modified | month | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| [2026-03-01 00:00:00+00:00, 2026-04-01 00:00:00+00:00] | False | 174-9F5 | 611308f5-2e5d-4a93-b20e-51d58925bc3a | None | Monthly overview | month_example | TM_month_UTC | [sdk] | service-account-sdktest | 2026-09-18 15:09:17.316000+00:00 | 2026-09-18 15:09:17.316000+00:00 | March |
| [2026-04-01 00:00:00+00:00, 2026-05-01 00:00:00+00:00] | False | 174-9F6 | 433cdc49-d53c-489a-b510-a4e783e02ed4 | None | Monthly overview | month_example | TM_month_UTC | [sdk] | service-account-sdktest | 2026-09-18 15:09:17.316000+00:00 | 2026-09-18 15:09:17.316000+00:00 | April |
| [2026-05-01 00:00:00+00:00, 2026-06-01 00:00:00+00:00] | False | 174-9F7 | 2405e739-ab13-4bba-9cf6-d6b14ac96210 | None | Monthly overview | month_example | TM_month_UTC | [sdk] | service-account-sdktest | 2026-09-18 15:09:17.316000+00:00 | 2026-09-18 15:09:17.316000+00:00 | May |
| [2026-06-01 00:00:00+00:00, 2026-07-01 00:00:00+00:00] | False | 174-9F8 | de3a976e-6be7-4de2-9ab3-c32f8a1ca625 | None | Monthly overview | month_example | TM_month_UTC | [sdk] | service-account-sdktest | 2026-09-18 15:09:17.316000+00:00 | 2026-09-18 15:09:17.316000+00:00 | June |
| [2026-07-01 00:00:00+00:00, 2026-08-01 00:00:00+00:00] | False | 174-9F9 | c75626a9-a94b-4efd-8316-109ce89457ce | None | Monthly overview | month_example | TM_month_UTC | [sdk] | service-account-sdktest | 2026-09-18 15:09:17.316000+00:00 | 2026-09-18 15:09:17.316000+00:00 | July |
| [2026-08-01 00:00:00+00:00, 2026-09-01 00:00:00+00:00] | False | 174-9FA | f6869dc8-c4cf-4b1c-acc2-519860ee3f04 | None | Monthly overview | month_example | TM_month_UTC | [sdk] | service-account-sdktest | 2026-09-18 15:09:17.317000+00:00 | 2026-09-18 15:09:17.317000+00:00 | August |
To update items, modify the DataFrame and pass it to
client.context.item.update. Rows are
matched to existing items through the identifier column that get_items returned,
so keep that column intact.
df["description"] = "Updated item"
client.context.item.update(df)
The most efficient way to delete items is
ContextHubViewDefinition.delete_items,
which removes every item matching the definition's filters. This is powerful and easy
to misuse: with loose filters you can delete far more than you intended. As a
safeguard, always include at least a
ContextCreatedByFilter
scoped to specific users (usually yourself), so you never remove other users' items.
chv_def.delete_items()
Populating items from a search¶
A common pattern is to run a value-based search and turn
each result into a context item — often writing the search's calculations straight
into context fields. The call below reuses the search from that example and adds the
component and type columns that make its result a valid input for
create.
Watch out for open results (rows where the open column is True): these create
open context items that will never be closed, because there is no link back to the
originating search. To avoid items that stay open indefinitely, either drop the open
rows (creating no item for them) or clear/adjust the open column so a closed item is
written up to the current time.
now = pd.Timestamp.now(tz=client.tz)
search_interval=pd.Interval(
now - pd.Timedelta(days=180),
now,
)
vbs_def = client.search.value.define(
queries=[(month_tag, "constant")],
calculations={
"month": (month_tag, "start", ""),
}
)
df = vbs_def.get_results(search_interval).collect()
df = df[~df["open"]] # drop open results
df["component"] = month_tag
df["type"] = context_type
df["keywords"] = [keywords] * len(df)
df["description"] = description
df
| open | month | component | type | keywords | description | |
|---|---|---|---|---|---|---|
| (2026-03-22 15:10:00+00:00, 2026-03-31 23:59:00+00:00] | False | March | TM_month_UTC | month_example | [sdk] | Monthly overview |
| (2026-04-01 00:00:00+00:00, 2026-04-30 23:59:00+00:00] | False | April | TM_month_UTC | month_example | [sdk] | Monthly overview |
| (2026-05-01 00:00:00+00:00, 2026-05-31 23:59:00+00:00] | False | May | TM_month_UTC | month_example | [sdk] | Monthly overview |
| (2026-06-01 00:00:00+00:00, 2026-06-30 23:59:00+00:00] | False | June | TM_month_UTC | month_example | [sdk] | Monthly overview |
| (2026-07-01 00:00:00+00:00, 2026-07-31 23:59:00+00:00] | False | July | TM_month_UTC | month_example | [sdk] | Monthly overview |
| (2026-08-01 00:00:00+00:00, 2026-08-31 23:59:00+00:00] | False | August | TM_month_UTC | month_example | [sdk] | Monthly overview |
Creating items automatically with a monitor¶
Rather than writing historic items by hand, we can attach a monitor to a saved
search so that every new result automatically becomes a context item. We persist the
search with
client.search.value.create, take its
monitor with get_monitor, and configure the monitor's
context settings —
the type, component, description and keywords each new item should get. The same
technique is used as part of the Cross-asset rollout
example.
vbs = client.search.value.create(
name="SDK Example - Months",
definition=vbs_def,
)
monitor = vbs.get_monitor()
monitor.context.context_type = context_type
monitor.context.component = month_tag
monitor.context.description = description
monitor.context.keywords = keywords
monitor.context.enabled = True
monitor.update()
monitor.enable()
Backfilling without creating duplicates¶
Typically we want a monitor for new events and a one-off backfill of the historic events that predate it. The catch: naively re-running the backfill creates duplicate items every time. Rather than relying on running it exactly once, the robust approach is to cross-reference the items that already exist and create only the ones that are missing — the same upsert idea used in Managing work items.
# `df` still holds the items initialised from the search above.
# Retrieve every item that already exists (all pages).
df_existing = chv_def.get_items().collect(max_pages=None)
# Keep only the intervals that are not present yet, and create those.
df_new = df[~df.index.isin(df_existing.index)]
client.context.item.create(df_new)
Keeping fields up to date with an update service¶
Items created by a monitor cannot carry dynamic calculations. To enrich them with calculations that are only correct once the event has finished, run a small update script on a schedule.
Two practical points make that script both safe and cheap:
- Filter on the target field being empty (a
ContextStringFieldFilterin"empty"mode) so you only touch items that have not been updated yet. - Filter on the item being closed (a
ContextStateFilter), since a still-open result gives premature calculation results — an integral or average would only cover the event up to now.
It is also good practice to update the index of the tags your calculations need at the start of the script; see Indexing tags.
month_tag.index()
context_field = client.context.field.from_name("month")
field_filter = client.context.filter.string_field.new(
field=context_field,
mode="empty",
)
state_filter = client.context.filter.state.new(
mode="closed",
)
# Keep the previous filters, add empty field filter
context_filters_update = context_filters + [field_filter]
chv_def_update = client.context.view.define(
filters=context_filters_update,
)
# Get items, update the `month` field
df = chv_def_update.get_items().collect()
df = month_tag.add_aggregation(events=df, method="start", name="month")
client.context.item.update(df)