Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added functions to count module commands per run #14797

Merged
merged 2 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions abr-testing/abr_testing/automation/jira_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def issues_on_board(self, board_id: str) -> List[str]:
def open_issue(self, issue_key: str) -> None:
"""Open issue on web browser."""
url = f"{self.url}/browse/{issue_key}"
print(f"Opening at {url}.")
webbrowser.open(url)

def create_ticket(
Expand Down
39 changes: 12 additions & 27 deletions abr-testing/abr_testing/data_collection/abr_google_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import gspread # type: ignore[import]
from datetime import datetime, timedelta
from abr_testing.data_collection import read_robot_logs
from typing import Set, Dict, Any
from typing import Set, Dict, Any, Tuple, List
from abr_testing.automation import google_drive_tool, google_sheets_tool


Expand All @@ -31,7 +31,7 @@ def get_modules(file_results: Dict[str, str]) -> Dict[str, Any]:

def create_data_dictionary(
runs_to_save: Set[str], storage_directory: str
) -> Dict[Any, Dict[str, Any]]:
) -> Tuple[Dict[Any, Dict[str, Any]], List]:
"""Pull data from run files and format into a dictionary."""
runs_and_robots = {}
for filename in os.listdir(storage_directory):
Expand Down Expand Up @@ -100,12 +100,17 @@ def create_data_dictionary(
"Right Mount": right_pipette,
"Extension": extension,
}
row_2 = {**row, **all_modules}
tc_dict = read_robot_logs.thermocycler_commands(file_results)
hs_dict = read_robot_logs.hs_commands(file_results)
tm_dict = read_robot_logs.temperature_module_commands(file_results)
notes = {"Note1": "", "Note2": ""}
row_2 = {**row, **all_modules, **notes, **hs_dict, **tm_dict, **tc_dict}
headers = list(row_2.keys())
runs_and_robots[run_id] = row_2
else:
os.remove(file_path)
print(f"Run ID: {run_id} has a run time of 0 minutes. Run removed.")
return runs_and_robots
return runs_and_robots, headers


if __name__ == "__main__":
Expand Down Expand Up @@ -175,29 +180,9 @@ def create_data_dictionary(
run_ids_on_gd, run_ids_on_gs
)
# Add missing runs to google sheet
runs_and_robots = create_data_dictionary(missing_runs_from_gs, storage_directory)
headers = [
"Robot",
"Run_ID",
"Protocol_Name",
"Software Version",
"Date",
"Start_Time",
"End_Time",
"Run_Time (min)",
"Errors",
"Error_Code",
"Error_Type",
"Error_Instrument",
"Error_Level",
"Left Mount",
"Right Mount",
"Extension",
"heaterShakerModuleV1",
"temperatureModuleV2",
"magneticBlockV1",
"thermocyclerModuleV2",
]
runs_and_robots, headers = create_data_dictionary(
missing_runs_from_gs, storage_directory
)
read_robot_logs.write_to_local_and_google_sheet(
runs_and_robots, storage_directory, google_sheet_name, google_sheet, headers
)
10 changes: 6 additions & 4 deletions abr-testing/abr_testing/data_collection/abr_robot_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def get_error_info_from_robot(
# JIRA Ticket Fields
failure_level = "Level " + str(error_level) + " Failure"
components = [failure_level, "Flex-RABR"]
components = ["Flex-RABR"]
affects_version = results["API_Version"]
parent = results.get("robot_name", "")
print(parent)
Expand Down Expand Up @@ -141,10 +142,15 @@ def get_error_info_from_robot(
whole_description_str,
saved_file_path,
) = get_error_info_from_robot(ip, one_run, storage_directory)
# get calibration data
saved_file_path_calibration, calibration = read_robot_logs.get_calibration_offsets(
ip, storage_directory
)
print(f"Making ticket for run: {one_run} on robot {robot}.")
# TODO: make argument or see if I can get rid of with using board_id.
project_key = "RABR"
parent_key = project_key + "-" + robot[-1]
issues_ids = ticket.issues_on_board(board_id)
issue_url, issue_key = ticket.create_ticket(
summary,
whole_description_str,
Expand All @@ -158,8 +164,4 @@ def get_error_info_from_robot(
)
ticket.open_issue(issue_key)
ticket.post_attachment_to_ticket(issue_key, saved_file_path)
# get calibration data
saved_file_path_calibration, calibration = read_robot_logs.get_calibration_offsets(
ip, storage_directory
)
ticket.post_attachment_to_ticket(issue_key, saved_file_path_calibration)
8 changes: 4 additions & 4 deletions abr-testing/abr_testing/data_collection/error_levels.csv
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Prefix,Error Code,Description,Categories,Level of Failure,
2,2009,Early Capactivive Sense Trigger,A Robot Action Failed,4,
2,2010,Innacrruate Non Contact Sweep,A Robot Action Failed,3,
2,2011,Misaligned Gantry,A Robot Action Failed,3,
2,2012,Unmatched Tip Presence States,A Robot Action Failed,3-4,
2,2012,Unmatched Tip Presence States,A Robot Action Failed, 4,
2,2013,Position Unknown,A Robot Action Failed,4,
2,2014,Execution Cancelled,A Robot Action Failed, 4,
2,2015,Failed Gripper Pickup Error,A Robot Action Failed,3,
Expand All @@ -31,18 +31,18 @@ Prefix,Error Code,Description,Categories,Level of Failure,
3,3004,Tip Drop Failed,A Robot Interaction Failed,4,
3,3005,Unexpeted Tip Removal,A Robot Interaction Failed,4,
3,3006,Pipette Overpressure,A Robot Interaction Failed,3,
3,3008,E-Stop Activated,A Robot Interaction Failed,Not an error,
3,3008,E-Stop Activated,A Robot Interaction Failed,5, Not an error,
3,3009,E-Stop Not Present,A Robot Interaction Failed,5,
3,3010,Pipette Not Present,A Robot Interaction Failed,5,
3,3011,Gripper Not Present,A Robot Interaction Failed,5,
3,3012,Unexpected Tip Attach,A Robot Interaction Failed,4,
3,3013,Firmware Update Required,A Robot Interaction Failed,Not an error,
3,3013,Firmware Update Required,A Robot Interaction Failed,5, Not an error,
3,3014,Invalid ID Actuator,A Robot Interaction Failed,3,
3,3015,Module Not Pesent,A Robot Interaction Failed,5,Not an error
3,3016,Invalid Instrument Data,A Robot Interaction Failed,3,
3,3017,Invalid Liquid Class Name,A Robot Interaction Failed,5,Not an error
3,3018,Tip Detector Not Found,A Robot Interaction Failed,3,
4,4000,General Error,A Software Error Occured,2-4,How severe does a general error get
4,4000,General Error,A Software Error Occured,4,How severe does a general error get
4,4001,Robot In Use,A Software Error Occured,5,Not an error
4,4002,API Removed,A Software Error Occured,5,used an old app on a new robot
4,4003,Not Supported On Robot Type,A Software Error Occured,5,Not an error
Expand Down
214 changes: 210 additions & 4 deletions abr-testing/abr_testing/data_collection/read_robot_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
saved in a local directory.
"""
import csv
import datetime
from datetime import datetime
import os
from abr_testing.data_collection.error_levels import ERROR_LEVELS_PATH
from typing import List, Dict, Any, Tuple, Set
Expand All @@ -14,6 +14,210 @@
import requests


def command_time(command: Dict[str, str]) -> Tuple[float, float]:
"""Calculate total create and complete time per command."""
try:
create_time = datetime.strptime(
command.get("createdAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
start_time = datetime.strptime(
command.get("startedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
complete_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
create_to_start = (start_time - create_time).total_seconds()
start_to_complete = (complete_time - start_time).total_seconds()
except ValueError:
create_to_start = 0
start_to_complete = 0
return create_to_start, start_to_complete


def hs_commands(file_results: Dict[str, Any]) -> Dict[str, float]:
"""Gets total latch engagements, homes, rotations and total on time (sec) for heater shaker."""
# TODO: modify for cases that have more than 1 heater shaker.
commandData = file_results.get("commands", "")
hs_latch_count: float = 0.0
hs_temp: float = 0.0
hs_home_count: float = 0.0
hs_speed: float = 0.0
hs_rotations: Dict[str, float] = dict()
hs_temps: Dict[str, float] = dict()
temp_time = None
shake_time = None
for command in commandData:
commandType = command["commandType"]
# Heatershaker
# Latch count
if (
commandType == "heaterShaker/closeLabwareLatch"
or commandType == "heaterShaker/openLabwareLatch"
):
hs_latch_count += 1
# Home count
elif commandType == "heaterShaker/deactivateShaker":
hs_home_count += 1
deactivate_time = datetime.strptime(
command.get("startedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
if temp_time is not None and deactivate_time > temp_time:
temp_duration = (deactivate_time - temp_time).total_seconds()
hs_temps[hs_temp] = hs_temps.get(hs_temp, 0.0) + temp_duration
if shake_time is not None and deactivate_time > shake_time:
shake_duration = (deactivate_time - shake_time).total_seconds()
hs_rotations[hs_speed] = hs_rotations.get(hs_speed, 0.0) + (
(hs_speed * shake_duration) / 60
)
# of Rotations
elif commandType == "heaterShaker/setAndWaitForShakeSpeed":
hs_speed = command["params"]["rpm"]
shake_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
# On Time
elif commandType == "heaterShaker/setTargetTemperature":
# if heater shaker temp is not deactivated.
hs_temp = command["params"]["celsius"]
temp_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)

hs_total_rotations = sum(hs_rotations.values())
hs_total_temp_time = sum(hs_temps.values())
hs_dict = {
"Heatershaker # of Latch Engagements": hs_latch_count,
"Heatershaker # of Homes": hs_home_count,
"Heatershaker # of Rotations": hs_total_rotations,
"Heatershaker Temp On Time (sec)": hs_total_temp_time,
}
return hs_dict


def temperature_module_commands(file_results: Dict[str, Any]) -> Dict[str, float]:
"""Get # of temp changes and total temp on time for temperature module from run log."""
# TODO: modify for cases that have more than 1 temperature module.
tm_temp_change = 0
tm_temps: Dict[str, float] = dict()
temp_time = None
deactivate_time = None
commandData = file_results.get("commands", "")
for command in commandData:
commandType = command["commandType"]
if commandType == "temperatureModule/setTargetTemperature":
tm_temp = command["params"]["celsius"]
tm_temp_change += 1
if commandType == "temperatureModule/waitForTemperature":
temp_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
if commandType == "temperatureModule/deactivate":
deactivate_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
if temp_time is not None and deactivate_time > temp_time:
temp_duration = (deactivate_time - temp_time).total_seconds()
tm_temps[tm_temp] = tm_temps.get(tm_temp, 0.0) + temp_duration
if temp_time is not None and deactivate_time is None:
# If temperature module is not deactivated, protocol completedAt time stamp used.
protocol_end = datetime.strptime(
file_results.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
temp_duration = (protocol_end - temp_time).total_seconds()
tm_temps[tm_temp] = tm_temps.get(tm_temp, 0.0) + temp_duration
tm_total_temp_time = sum(tm_temps.values())
tm_dict = {
"Temp Module # of Temp Changes": tm_temp_change,
"Temp Module Temp On Time (sec)": tm_total_temp_time,
}
return tm_dict


def thermocycler_commands(file_results: Dict[str, Any]) -> Dict[str, float]:
"""Counts # of lid engagements, temp changes, and temp sustaining mins."""
# TODO: modify for cases that have more than 1 thermocycler.
commandData = file_results.get("commands", "")
lid_engagements: float = 0.0
block_temp_changes: float = 0.0
lid_temp_changes: float = 0.0
lid_temps: Dict[str, float] = dict()
block_temps: Dict[str, float] = dict()
lid_on_time = None
lid_off_time = None
block_on_time = None
block_off_time = None
for command in commandData:
commandType = command["commandType"]
if (
commandType == "thermocycler/openLid"
or commandType == "thermocycler/closeLid"
):
lid_engagements += 1
if commandType == "thermocycler/setTargetBlockTemperature":
block_temp = command["params"]["celsius"]
block_temp_changes += 1
block_on_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
if commandType == "thermocycler/setTargetLidTemperature":
lid_temp_changes += 1
lid_temp = command["params"]["celsius"]
lid_on_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
if commandType == "thermocycler/deactivateLid":
lid_off_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
if lid_on_time is not None and lid_off_time > lid_on_time:
lid_duration = (lid_off_time - lid_on_time).total_seconds()
lid_temps[lid_temp] = lid_temps.get(lid_temp, 0.0) + lid_duration
if commandType == "thermocycler/deactivateBlock":
block_off_time = datetime.strptime(
command.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
if block_on_time is not None and block_off_time > block_on_time:
block_duration = (block_off_time - block_on_time).total_seconds()
block_temps[block_temp] = (
block_temps.get(block_temp, 0.0) + block_duration
)
if commandType == "thermocycler/runProfile":
profile = command["params"]["profile"]
total_changes = len(profile)
block_temp_changes += total_changes
for cycle in profile:
block_temp = cycle["celsius"]
block_time = cycle["holdSeconds"]
block_temps[block_temp] = block_temps.get(block_temp, 0.0) + block_time
if block_on_time is not None and block_off_time is None:
# If thermocycler block not deactivated protocol completedAt time stamp used
protocol_end = datetime.strptime(
file_results.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
temp_duration = (protocol_end - block_on_time).total_seconds()
block_temps[block_temp] = block_temps.get(block_temp, 0.0) + temp_duration
if lid_on_time is not None and lid_off_time is None:
# If thermocycler lid not deactivated protocol completedAt time stamp used
protocol_end = datetime.strptime(
file_results.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z"
)
temp_duration = (protocol_end - lid_on_time).total_seconds()
lid_temps[lid_temp] = block_temps.get(lid_temp, 0.0) + temp_duration

block_total_time = sum(block_temps.values())
lid_total_time = sum(lid_temps.values())

tc_dict = {
"Thermocycler # of Lid Engagements": lid_engagements,
"Thermocycler Block # of Temp Changes": block_temp_changes,
"Thermocycler Block Temp On Time (sec)": block_total_time,
"Thermocycler Lid # of Temp Changes": lid_temp_changes,
"Thermocycler Lid Temp On Time (sec)": lid_total_time,
}

return tc_dict


def create_abr_data_sheet(
storage_directory: str, file_name: str, headers: List[str]
) -> str:
Expand Down Expand Up @@ -112,7 +316,7 @@ def read_abr_data_sheet(
runs_in_sheet.add(run_id)
print(f"There are {str(len(runs_in_sheet))} runs documented in the ABR sheet.")
# Read Google Sheet
google_sheet.check_token()
google_sheet.token_check()
google_sheet.write_header(headers)
google_sheet.update_row_index()
return runs_in_sheet
Expand Down Expand Up @@ -189,7 +393,7 @@ def get_calibration_offsets(
health_data = response.json()
robot_name = health_data.get("name", "")
api_version = health_data.get("api_version", "")
pull_date_timestamp = datetime.datetime.now()
pull_date_timestamp = datetime.now()
date = pull_date_timestamp.date().isoformat()
file_date = str(pull_date_timestamp).replace(":", "").split(".")[0]
calibration["Robot"] = robot_name
Expand Down Expand Up @@ -219,5 +423,7 @@ def get_calibration_offsets(
)
deck: Dict[str, Any] = response.json()
calibration["Deck"] = deck.get("deckCalibration", "")
saved_file_path = save_run_log_to_json(ip, calibration, storage_directory)
save_name = ip + "_calibration.json"
saved_file_path = os.path.join(storage_directory, save_name)
json.dump(calibration, open(saved_file_path, mode="w"))
return saved_file_path, calibration
Loading