{
"cells": [
{
"cell_type": "markdown",
"id": "f1826aa4-dbb5-4dfa-b82c-911731a06fc1",
"metadata": {},
"source": [
" # Webhook Notification Response Schema\n",
"\n",
" The JSON structure returned by the Seeq Condition Monitor webhook notification endpoint.\n",
"\n",
" | Property | Type | Nullable | Description |\n",
" |----------|------|----------|-------------|\n",
" | `notificationConfiguration` | `NotificationConfiguration` | Yes | Configuration details for the notification, including recipients and formatting options |\n",
" | `monitorName` | `str` | No | Name of the condition monitor that triggered this notification |\n",
" | `batch` | `BatchMeta` | No | Metadata about the webhook batch execution |\n",
" | `results` | `list[ResultForWebhook]` | No | List of results, one per monitored condition |\n",
"\n",
" ## NotificationConfiguration\n",
"\n",
" Configuration details for the notification, including recipients and formatting options\n",
"\n",
" | Property | Type | Nullable | Description |\n",
" |----------|------|----------|-------------|\n",
" | `notificationTriggerId` | `str` | No | UUID of the trigger (monitor) that generated this notification |\n",
" | `contextualText` | `str` | Yes | A string with additional information configured on a notification like a link to a workbook |\n",
" | `timezone` | `str` | Yes | A string timezone for the condition monitor |\n",
" | `capsuleGrouping` | `str` | Yes | How capsules are grouped in notifications:
• `\"CONDITION\"` - One notification per condition
• `\"CAPSULE\"` - One notification per capsule found
• `\"ALL\"` - All results in one notification |\n",
" | `capsuleProperties` | `list[str]` | No | List of capsule property names to include in notifications |\n",
"\n",
" ## BatchMeta\n",
"\n",
" Metadata about the webhook batch execution\n",
"\n",
" | Property | Type | Nullable | Description |\n",
" |----------|------|----------|-------------|\n",
" | `id` | `str` | No | Unique identifier for this monitor run. Format: `{monitorId}_{epochMs}` |\n",
" | `total` | `int` | No | Total number of webhook calls that will be made in this monitor run |\n",
" | `number` | `int` | No | Current batch number (1-based indexing; first batch is 1, last equals `total`) |\n",
" | `stats` | `Stats` | No | Statistics about the data in this batch |\n",
" | `isLastBatch` | `bool` | No | `True` if this is the final batch (computed as `number == total`) |\n",
"\n",
" ### Stats\n",
"\n",
" Statistics about the data in this batch\n",
"\n",
" | Property | Type | Nullable | Description |\n",
" |----------|------|----------|-------------|\n",
" | `conditionCount` | `int` | No | Number of conditions (series) in this batch |\n",
" | `capsuleCount` | `int` | No | Total number of capsules across all conditions in this batch |\n",
" \n",
" ## ResultForWebhook\n",
"\n",
" List of results, one per monitored condition\n",
"\n",
" | Property | Type | Nullable | Description |\n",
" |----------|------|----------|-------------|\n",
" | `capsuleEvents` | `list[CapsuleEvent]` | No | List of capsule events detected by the condition monitor |\n",
" | `hasMoreCapsules` | `bool` | No | Indicates if additional capsules exist beyond this batch (defaults to `False`) |\n",
" | `errors` | `list[str]` | No | List of error messages encountered during monitoring |\n",
" | `conditionName` | `str` | Yes | Name of the monitored condition/series |\n",
"\n",
" ### CapsuleEvent\n",
"\n",
" List of capsule events detected by the condition monitor\n",
"\n",
" | Property | Type | Nullable | Description |\n",
" |----------|------|----------|-------------|\n",
" | `start` | `Value` | Yes | Start time of the capsule as a nanosecond timestamp |\n",
" | `end` | `Value` | Yes | End time of the capsule as a nanosecond timestamp |\n",
" | `updatedAt` | `Value` | Yes | Timestamp when this capsule was last updated (nanoseconds) |\n",
" | `conditionGuid` | `str` | No | UUID of the condition that generated this capsule |\n",
" | `type` | `str` | No | Event type: `\"NEW\"`, `\"BECAME_CERTAIN\"`, `\"EXTINCT\"`, or `\"STILL_UNCERTAIN\"` |\n",
" | `propertyNames` | `list[str]` | No | Names of custom properties attached to this capsule |\n",
" | `isUncertain` | `bool` | No | Whether the capsule is uncertain (defaults to `False`) |\n",
" | `isBounded` | `bool` | No | Whether the capsule has both start and end times (defaults to `True`) |\n",
" | `isSuppressed` | `bool` | No | Whether this capsule event has been suppressed (defaults to `False`) |\n",
" | `properties` | `dict[str, Value]` | No | Custom properties and their values for this capsule |\n",
" | `id` | `str` | Yes | Unique identifier for this capsule |\n",
"\n",
" #### Value\n",
"\n",
" Represents a typed value with unit of measurement information.\n",
"\n",
" | Property | Type | Nullable | Description |\n",
" |----------|------|----------|-------------|\n",
" | `type` | `str` | No | Data type: `\"STRING\"`, `\"BOOLEAN\"`, `\"LONG\"`, or `\"DOUBLE\"` |\n",
" | `uom` | `str` | No | Unit of measurement (e.g., `\"ns\"` for nanoseconds, `\"\"` for unitless) |\n",
" | `value` | `bool \\| int \\| float \\| str` | No | The actual value (type matches the `type` field) |\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3b81e0aa-a46b-4e68-98d8-d607defcbddf",
"metadata": {},
"outputs": [],
"source": [
"import traceback\n",
"import requests\n",
"import json\n",
"import pandas as pd\n",
"from datetime import datetime, timezone"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8d7513ff-16d4-409d-951a-30960573606a",
"metadata": {},
"outputs": [],
"source": [
"def convert_ns_to_seconds(nanoseconds: int) -> float:\n",
" \"\"\"Convert nanoseconds to seconds.\"\"\"\n",
" return nanoseconds / 1_000_000_000"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3e8e681-7fb1-4dab-b952-9e76fb4f0cf5",
"metadata": {},
"outputs": [],
"source": [
"def convert_nanos_to_timestamp(time_ns: int, timezone=timezone.utc) -> str:\n",
" \"\"\"Convert nanoseconds to timestamp string.\"\"\"\n",
" time_s = convert_ns_to_seconds(time_ns)\n",
"\n",
" # Create a datetime object from the seconds since epoch\n",
" timestamp = datetime.fromtimestamp(time_s, tz=timezone)\n",
" timestamp_string=timestamp.strftime('%Y-%m-%d %H:%M:%S %Z')\n",
"\n",
" return timestamp_string"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "63cbcfb3-a4fa-4f73-871b-230ce28eb371",
"metadata": {},
"outputs": [],
"source": [
"def compute_duration(start: int, end: int, unit: str = 'ns') -> str:\n",
" \"\"\"Compute the duration of a capsule\"\"\"\n",
" return str(pd.Timestamp(end, unit=unit) - pd.Timestamp(start, unit=unit)) if start and end else ''"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a45b8524-75b0-4cec-ac8f-5fc43bc105c4",
"metadata": {},
"outputs": [],
"source": [
"def process_webhook(payload: dict):\n",
" \"\"\"Process a webhook notification payload.\"\"\"\n",
" text = \"\"\n",
" try:\n",
" condition_monitor_name = payload['monitorName']\n",
" \n",
" text = f\":scream: *{condition_monitor_name}:*\\n\\t\"\n",
" \n",
" # Iterate through results for each condition\n",
" for result in payload['results']:\n",
" condition_name = result['conditionName']\n",
" \n",
" # Process capsule events\n",
" for event in result['capsuleEvents']:\n",
" start_ts = convert_nanos_to_timestamp(event['start']['value']) if event['start'] else ''\n",
" end_ts = convert_nanos_to_timestamp(event['end']['value']) if event['end'] else ''\n",
" duration_td = compute_duration(start_ts, end_ts)\n",
" \n",
" start = '' if not start_ts else f\"Start: {start_ts}\\n\\t\"\n",
" end = '' if not end_ts else f\"End: {end_ts}\\n\\t\"\n",
" duration = '' if not duration_td else f\"Duration: {duration_td}\\n\\t\" \n",
" \n",
" text += f\"Condition: {condition_name}\\n\\t\"\n",
" text += (start + end + duration)\n",
" \n",
" # Access custom properties\n",
" for prop_name, prop_value in event['properties'].items():\n",
" text += f\"{prop_name}: {prop_value['value']}\\n\\t\"\n",
" \n",
" text += \"\\n\"\n",
" except Exception as e:\n",
" LOG.error(f\"Error found: {str(e)}: {traceback.format_exc()}\")\n",
" text += f\"Error: {str(e)}\\n\"\n",
" finally:\n",
" return text"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae21a161-50df-4b0a-b8be-e201d525c5a4",
"metadata": {},
"outputs": [],
"source": [
"# POST /condition_monitors_template\n",
"data = REQUEST['body']\n",
"result = process_webhook(data)\n",
"\n",
"body = {'text': result}\n",
"LOG.info(f\"Result: {body}\")\n",
"url = 'https://hooks.slack.com/services/xxxxxxxxx/yyyyyyyyyyy/zzzzzzzzzzzzzzzzzzzzzzzz'\n",
"response = requests.post(url, json.dumps(body))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.11",
"language": "python",
"name": "python311"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.14"
}
},
"nbformat": 4,
"nbformat_minor": 5
}