{ "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 }