Event-Triggered Compliance Training
Assign training courses based on HOS violations
Overview
This script automates the process of assigning training courses to drivers based on their HOS violations. It interacts with Samsara APIs to fetch drivers based on tags, check existing assignments, and create new assignments for drivers based on their HOS violations.
Note: This guide assumes you are already familiar with the basics of the Samsara Training Product and the overall GET Training Assignments, POST Training Assignments and GET Training Courses APIs. If you are new to Training, see the Driver Training Guide first.
Example Use Cases
- A Safety manager wants to assign the "Compliance Training" course to a driver when the driver has 2 or more HOS violations in the past 30 days.
- A Fleet manager wants to assign the "Compliance Training" course to a driver when the driver has 3 HOS violations that are marked as "shiftHour" type in the past 45 days.
Trigger Conditions
- Specific HOS violation type (e.g., "shiftHours")
- Specify conditions under which assignments will skip drivers (e.g., skip assignment if the driver has completed or is enrolled in the course within the past 30 days)
- Specify driver tags for assignment
- HOS violation count thresholds are exceeded (e.g., 2 violations in 45 days)
Action
- Assign training course with specific due dates
Execution Flow
- Fetch HOS Violations: Convert HOS_VIOLATION_LOOKBACK_START and HOS_VIOLATION_LOOKBACK_END to RFC3339 format. Retrieve HOS violation events that match HOS_VIOLATION_TYPE for the given period and driver tag ID. Count the number of violations for each driver.
- Filter Drivers Based on HOS Violation Threshold: Identify drivers who have exceeded the HOS_VIOLATION_THRESHOLD. Flag these drivers for training.
- Identify Drivers Who Require Training: Loop through the drivers that meet the above thresholds. If a driver has already been assigned the course, omit those drivers from drivers_requiring_training.
- Generate Training Due Date: Call generate_rfc3339_timestamp() with COURSE_COMPLETION_DUE_DAYS to get the training due date in RFC3339 format.
- Assign Training: Call assign_training() to assign the course to drivers in drivers_requiring_training with the generated training due date.
Script
Note: To run certain automations, you will need to set up a development environment. Ensure your environment has access to Samsara API endpoints and is properly configured for integration. These code snippets can be copied but will need to be customized to match your organization's specific use case, including correct API tokens and organizational details.
from datetime import datetime, timedelta
import requests
# API URLs and token
HOS_VIOLATION_API = 'https://api.samsara.com/fleet/hos/violations'
ASSIGN_TRAINING_API = 'https://api.samsara.com/training-assignments'
STREAM_TRAINING_ASSIGNMENT_API = 'https://api.samsara.com/training-assignments/stream'
API_TOKEN = 'Insert your API token'
HEADERS = {'Accept': 'application/json', 'Authorization': f'Bearer {API_TOKEN}'}
# Constants
HOS_VIOLATION_LOOKBACK_START = -45
HOS_VIOLATION_LOOKBACK_END = 0
TARGET_DRIVER_TAG_IDS = ['Insert the driver tag ID']
HOS_VIOLATION_TYPE = 'Insert violation type'
HOS_VIOLATION_THRESHOLD = 3
COURSE_ID = 'Insert course ID'
COURSE_COMPLETION_DUE_DAYS = 14
TRAINING_LOOKBACK_DAYS = -30
def get_hos_violations(start_time, end_time, tag_ids):
'''
Fetches Hours of Service (HOS) violations based on a specific time range and tag IDs.
Args:
start_time (str): Start time in RFC3339 format.
end_time (str): End time in RFC3339 format.
tag_ids (list): List of tag IDs to filter the data.
Returns:
list: A list of HOS violations within the specified time range and tags.
'''
params = {
'startTime': start_time,
'endTime': end_time,
'tagIds': ','.join(tag_ids),
'types': HOS_VIOLATION_TYPE,
}
hos_violations = []
after = ''
has_next_page = True
while has_next_page:
params['after'] = after
response = requests.get(HOS_VIOLATION_API, headers=HEADERS, params=params)
data = response.json()
hos_violations.extend(data['data'])
if data.get('pagination', {}).get('hasNextPage') == True:
has_next_page = True
after = data['pagination']['endCursor']
else:
has_next_page = False
return hos_violations
def assign_training(driver_ids, course_id, due_date):
'''
Assigns a specific training course to the provided drivers with a due date.
Args:
driver_ids (list): List of driver IDs to whom the training should be assigned.
course_id (str): The ID of the course to be assigned.
due_date (str): The due date for completing the course in RFC3339 format.
Returns:
None
'''
params = {'learnerIds': driver_ids, 'courseId': course_id, 'dueAtTime': due_date}
requests.post(ASSIGN_TRAINING_API, headers=HEADERS, params=params)
def get_training_assignment_details(assignment_interval_timestamp, course_id):
'''
Retrieves a stream of filtered training assignments within a specified time range.
Args:
assignment_interval_timestamp (str): Start timestamp in RFC3339 format to
filter the training assignments.
course_id (str): The ID of the training course to filter the assignments.
Returns:
list: A list of training assignments that match the provided filters.
'''
training_assignments = []
after = ''
params = {'startTime': assignment_interval_timestamp, 'courseIds': [course_id]}
has_next_page = True
while has_next_page:
params['after'] = after
response = requests.get(
STREAM_TRAINING_ASSIGNMENT_API, headers=HEADERS, params=params
)
data = response.json()
training_assignments.extend(data['data'])
if data.get('pagination', {}).get('hasNextPage'):
has_next_page = True
after = data['pagination']['endCursor']
else:
has_next_page = False
return training_assignments
def generate_rfc3339_timestamp(delta_days):
'''
Generates an RFC3339 timestamp by adding or subtracting days from the current date.
Args:
delta_days (int): Number of days to add (positive) or subtract (negative) from the
current date.
Returns:
str: The RFC3339 formatted timestamp.
'''
target_date = (datetime.now() + timedelta(days=delta_days)).replace(
hour=0, minute=0, second=0, microsecond=0
)
return target_date.strftime('%Y-%m-%dT%H:%M:%SZ')
def generate_milliseconds_timestamp(delta_days):
'''
Generates a timestamp in milliseconds format by adding or subtracting
days from the current date.
Args:
delta_days (int): Number of days to add (positive) or subtract(negative)
from the current date.
Returns:
int: The timestamp in milliseconds.
'''
target_date = (datetime.now() + timedelta(days=delta_days)).replace(
hour=0, minute=0, second=0, microsecond=0
)
return int(target_date.timestamp()) * 1000
def main():
# Generate RFC3339 timestamps for the HOS violation lookback start and end times
start_time_rfc3339 = generate_rfc3339_timestamp(HOS_VIOLATION_LOOKBACK_START)
end_time_rfc3339 = generate_rfc3339_timestamp(HOS_VIOLATION_LOOKBACK_END)
# Fetch HOS violation events for the specified lookback period and target drivers
hos_violation_events = get_hos_violations(
start_time_rfc3339, end_time_rfc3339, TARGET_DRIVER_TAG_IDS
)
# Track the count of HOS violations per driver
driver_hos_violation_count = {}
for event in hos_violation_events:
violations = event['violations']
for violation in violations:
driver_id = violation['driver']['id']
if driver_id not in driver_hos_violation_count:
driver_hos_violation_count[driver_id] = 1
else:
driver_hos_violation_count[driver_id] += 1
# Flag drivers who exceed the HOS violation threshold
flagged_drivers = {
driver_id
for driver_id, count in driver_hos_violation_count.items()
if count >= HOS_VIOLATION_THRESHOLD
}
# Fetch the details of drivers who have already been assigned training
drivers_assigned_training = []
training_lookback_days_rfc3339 = generate_rfc3339_timestamp(TRAINING_LOOKBACK_DAYS)
assignment_details = get_training_assignment_details(
training_lookback_days_rfc3339, COURSE_ID
)
for assignment in assignment_details:
if assignment['learner']['type'] == 'driver':
drivers_assigned_training.append(assignment['learner']['id'])
# Identify drivers who are flagged for violations and haven't been assigned training
drivers_requiring_training = [
driver for driver in flagged_drivers if driver not in drivers_assigned_training
]
drivers_requiring_training = ','.join(
[f'driver-{id}' for id in drivers_requiring_training]
)
# Generate RFC3339 timestamp for the course completion due date (14 days from now)
course_due_date_rfc3339 = generate_rfc3339_timestamp(COURSE_COMPLETION_DUE_DAYS)
# Assign the training to the filtered drivers requiring it
assign_training(drivers_requiring_training, COURSE_ID, course_due_date_rfc3339)
if __name__ == '__main__':
main()
Appendix
Constant Definitions
Constant | Type | Description |
---|---|---|
HOS_VIOLATION_LOOKBACK_START | Integer | Defines how many days back to look for HOS violation events. |
HOS_VIOLATION_LOOKBACK_END | Integer | Defines the end date for looking up HOS violation events. |
TARGET_DRIVER_TAG_IDS | List of Strings | List of tag IDs used to filter specific drivers. |
HOS_VIOLATION_TYPE | String | Define the specific HOS violation type to track. |
HOS_VIOLATION_THRESHOLD | Integer | A threshold used to filter drivers based on the count of HOS violation. Only drivers exhibiting the behavior at or above this threshold are considered for training. |
COURSE_ID | String | ID of the training course that will be assigned. |
COURSE_COMPLETION_DUE_DAYS | Integer | Number of days from today to set as the due date for training assignments. |
TRAINING_LOOKBACK_DAYS | Integer | Number of days to look back for training assignments. |
Function Definitions
Function | Description |
---|---|
get_hos_violations | Fetches the HOS violation events for a given time range, tag IDs and violation type. It retrieves all available pages of data using pagination. |
assign_training | This function is responsible for creating training assignments for drivers. It accepts three parameters: course_id, which is the ID of the training course to be assigned, driver_ids, which is a list of driver IDs to receive the training, and due_date, which is the due date for completing the training. |
get_training_assignment_details | This function retrieves a stream of training assignments filtered by a specified assignment_interval_timestamp and course_id. It handles pagination to fetch all relevant assignment data from the Samsara API. If successful, it returns a list of assignment records. |
generate_rfc3339_timestamp | This function generates a timestamp in milliseconds format by either adding or subtracting a specified number of days (given by delta_days) from the current date. To calculate a date in the past, pass a negative value for delta_days; for a future date, pass a positive value. This allows the function to determine whether the days should be added or subtracted. The timestamp generated in this example is midnight and in the UTC timezone. Please refer to Samsara Timestamp Documentation for more information on this. |
generate_milliseconds_timestamp | This function generates a timestamp in milliseconds format by either adding or subtracting a specified number of days (given by delta_days) from the current date. To calculate a date in the past, pass a negative value for delta_days; for a future date, pass a positive value. This allows the function to determine whether the days should be added or subtracted.. |
Updated 5 days ago