PKtOx0JJrtm/__init__.py""" Validate a Requirements Trace Matrix """ __version__ = "0.1.13" PK=pO*^22 rtm/api.pyimport click import time from rtm.path_handling import get_rtm_path from rtm.rtm_worksheet import RTMWorksheet from rtm.exceptions import RTMValidatorError def validate(path_option='default'): click.clear() click.echo( "\nWelcome to the DePuy Synthes Requirements Trace Matrix (RTM) Validator." "\nPlease select an RTM excel file you wish to validate." ) time.sleep(2) try: path = get_rtm_path(path_option) worksheet = RTMWorksheet(path) worksheet.validate() except RTMValidatorError as e: click.echo(e) click.echo( "\nThank you for using the RTM Validator." "\nIf you have questions or suggestions, please contact a Roebling team member." ) if __name__ == "__main__": validate() PK=pO ZZ rtm/cli.pyimport click from rtm import api @click.command() def main(): api.validate() PK\qO,rtm/exceptions.pyclass RTMValidatorError(Exception): pass class RTMValidatorFileError(RTMValidatorError): """ Raise this for any errors related to the excel file itself. Examples: wrong file extension file missing missing worksheet """ pass PK=pO,D]33rtm/path_handling.pyimport click import tkinter as tk from tkinter import filedialog from pathlib import Path import rtm.exceptions as exc def get_rtm_path(path_option='default') -> Path: if path_option == 'default': path = get_new_path_from_dialog() required_extensions = '.xlsx .xls'.split() if str(path) == '.': raise exc.RTMValidatorFileError("\nError: You didn't select a file") if path.suffix not in required_extensions: raise exc.RTMValidatorFileError( f"\nError: You didn't select a file with " f"a proper extension: {required_extensions}" ) click.echo(f"\nThe RTM you selected is {path}") return path elif isinstance(path_option, Path): return path_option def get_new_path_from_dialog() -> Path: root = tk.Tk() root.withdraw() path = Path(filedialog.askopenfilename()) return path if __name__ == "__main__": try: get_rtm_path() except exc.RTMValidatorError as e: print(e) PKsOrtm/rtm_worksheet.pyimport click from rtm.fields.field import Field from rtm.fields.field_subclasses import field_classes as fc from typing import List from rtm.worksheet_columns import get_worksheet_columns class RTMWorksheet: # Initialize each field with its data def __init__(self, path): worksheet_columns = get_worksheet_columns( path, worksheet_name="Procedure Based Requirements" ) self.fields = self._initialize_fields(fc, worksheet_columns) @staticmethod def _initialize_fields(field_classes, worksheet_columns) -> List[Field]: """Get list of field objects that each contain their portion of the worksheet_columns""" fields = [] with click.progressbar(field_classes) as bar: # fc.append(field(worksheet_columns)) # return for field in bar: fields.append(field(worksheet_columns)) # fields = [field(worksheet_columns) for field in bar] return fields def validate(self): # --- Check Field Sorting --------------------------------------------- index_current = -1 for field in self.fields: index_current = field.validate_position(index_current) # --- Validate Fields and Print Results ------------------------------- for field in self.fields: field.validate() if __name__ == "__main__": pass PK\qONrtm/worksheet_columns.py# --- Standard Library Imports ------------------------------------------------ from collections import namedtuple from typing import List # --- Third Party Imports ----------------------------------------------------- import openpyxl # --- Intra-Package Imports --------------------------------------------------- from rtm.exceptions import RTMValidatorFileError WorksheetColumn = namedtuple("WorksheetColumn", "header body index column") def get_worksheet_columns(path, worksheet_name): """Return list of WorksheetColumn objects""" # --- Get Workbook ---------------------------------------------------- wb = openpyxl.load_workbook(filename=str(path), read_only=True, data_only=True) # --- Get Worksheet --------------------------------------------------- ws = None for sheetname in wb.sheetnames: if sheetname.lower() == worksheet_name.lower(): ws = wb[sheetname] if ws is None: raise RTMValidatorFileError( f"\nError: Workbook does not contain a '{worksheet_name}' worksheet" ) # --- Convert Worksheet to WorksheetColumn objects -------------------- ws_data = [] start_column_num = 1 for index, col in enumerate(range(start_column_num, ws.max_column + 1)): column_header = ws.cell(1, col).value column_body = tuple(ws.cell(row, col).value for row in range(2, ws.max_row + 1)) ws_column = WorksheetColumn( header=column_header, body=column_body, index=index, column=col ) ws_data.append(ws_column) return ws_data def get_matching_worksheet_columns( sequence_worksheet_columns, field_name: str ) -> List[WorksheetColumn]: """Called by constructor to get matching WorksheetColumn objects""" matching_worksheet_columns = [ ws_col for ws_col in sequence_worksheet_columns if ws_col.header.lower() == field_name.lower() ] return matching_worksheet_columns def get_first_matching_worksheet_column( sequence_worksheet_columns, field_name: str ) -> WorksheetColumn: cols = get_matching_worksheet_columns(sequence_worksheet_columns, field_name) return cols[0] PKsOrtm/fields/__init__.pyPKsODMrtm/fields/cascade_block.pyfrom rtm.fields.field import Field class CascadeBlock(Field): def __init__(self, all_worksheet_columns): # --- Get Matching Subfields; Stop after first Non-Found -------------- self._subfields = [] for subfield_name in self._get_subfield_names(): subfield = CascadeSubfield(all_worksheet_columns, subfield_name) if subfield.field_found(): self._subfields.append(subfield) else: break # --- Get All Matching Columns ---------------------------------------- # --- Set Defaults ---------------------------------------------------- # --- Override defaults if matches are found -------------------------- pass @staticmethod def _get_subfield_names(): field_names = ["Procedure Step", "User Need", "Design Input"] for i in range(1, 20): field_names.append("DO Solution L" + str(i)) return field_names def validate_position(self, previous_index): """ Check that first subfield comes after the previous one and that each subfield appears in order. Return column number of last subfield """ # --- Check that subfields appear in order ---------------------------- for subfield in self._subfields: previous_index = subfield.validate_position(previous_index) return previous_index class CascadeSubfield(Field): def __init__(self, all_worksheet_columns, subfield_name): self.subfield_name = subfield_name super().__init__(all_worksheet_columns) def get_field_name(self): return self.subfield_namePK=pO* S  rtm/fields/field.pyimport rtm.fields.validation as val from rtm.fields.validation_results import print_validation_report from typing import List from rtm.worksheet_columns import get_matching_worksheet_columns class Field: field_name = None def __init__(self, all_worksheet_columns): # --- Get All Matching Columns ---------------------------------------- # matching_worksheet_columns = [ # (index, ws_col) # for index, ws_col in enumerate(all_worksheet_columns) # if ws_col.header.lower() == self.get_field_name().lower() # ] matching_worksheet_columns = get_matching_worksheet_columns(all_worksheet_columns, self.get_field_name()) # --- Set Defaults ---------------------------------------------------- self._indices = None # Used in later check of relative column position self._body = None # column data self._correct_position = None # --- Override defaults if matches are found -------------------------- if len(matching_worksheet_columns) >= 1: # indices, worksheet_columns = zip(*matching_worksheet_columns) # Get all matching indices (used for checking duplicate data and proper sorting) self._indices = [col.index for col in matching_worksheet_columns] # Get first matching column data (any duplicate columns are ignored; user rcv warning) self._body = matching_worksheet_columns[0].body def validate(self) -> None: """Called by RTMWorksheet object to val-check and report out on field.""" val_results = [val.val_column_exist(self.field_found())] if self.field_found(): val_results.append(val.val_column_sort(self._correct_position)) val_results += self._validate_this_field() print_validation_report(self.field_name, val_results) def _validate_this_field(self) -> List[dict]: return [] def field_found(self): if self._body is None: return False return True def _get_index(self): return self._indices[0] def validate_position(self, previous_index): """Check that this field comes after the previous one. Return this column number.""" if not self.field_found(): return previous_index if self._get_index() > previous_index: self._correct_position = True else: self._correct_position = False return self._get_index() @classmethod def get_field_name(cls): return cls.field_name if __name__ == "__main__": list = ['1'] second = '2' third = ['3', '4'] list.append(second) print(list) # list.append(third) print(list+third) PKsO_Grtm/fields/field_subclasses.pyfrom typing import List from rtm.fields.field import Field from rtm.worksheet_columns import WorksheetColumn import rtm.fields.validation as val field_classes = [] def collect_field(field): field_classes.append(field) return field @collect_field class ID(Field): field_name = "ID" def _validate_this_field(self) -> List[WorksheetColumn]: results = [ val.val_cells_not_empty(self._body), ] results += val.example_results() return results @collect_field class CascadeLevel(Field): field_name = "Cascade Level" def _validate_this_field(self) -> List[WorksheetColumn]: return val.example_results() @collect_field class ReqStatement(Field): field_name = "Requirement Statement" def _validate_this_field(self) -> List[WorksheetColumn]: return val.example_results() @collect_field class ReqRationale(Field): field_name = "Requirement Rationale" def _validate_this_field(self) -> List[WorksheetColumn]: return [val.val_cells_not_empty(self._body)] @collect_field class VVStrategy(Field): field_name = "Verification or Validation Strategy" def _validate_this_field(self) -> List[WorksheetColumn]: return val.example_results() @collect_field class VVResults(Field): field_name = "Verification or Validation Results" def _validate_this_field(self) -> List[WorksheetColumn]: return [] @collect_field class DOFeatures(Field): field_name = "Design Output Feature (with CTQ ID #)" def _validate_this_field(self) -> List[WorksheetColumn]: return [] @collect_field class CTQ(Field): field_name = "CTQ? Yes, No, N/A" def _validate_this_field(self) -> List[WorksheetColumn]: return [] @collect_field class Devices(Field): field_name = "Devices" def _validate_this_field(self) -> List[WorksheetColumn]: return [val.val_cells_not_empty(self._body)] if __name__ == "__main__": pass PK\qOOrtm/fields/validation.pyfrom typing import List from rtm.fields.validation_results import ValidationResult def val_column_sort(correct_position) -> ValidationResult: title = "Field Order" if correct_position: score = 'Pass' explanation = None else: score = 'Error' explanation = 'Action Required: Move this column to its correct position' return ValidationResult(score, title, explanation) def val_column_exist(field_found) -> ValidationResult: title = "Field Exist" if field_found: score = 'Pass' explanation = None else: score = 'Error' explanation = 'Field not found' return ValidationResult(score, title, explanation) def example_results() -> List[ValidationResult]: explanation = 'This is an example explanation' examples = [ ValidationResult('Pass', 'Pass Example', explanation), ValidationResult('Warning', 'Warning Example', explanation), ValidationResult('Error', 'Error Example', explanation), ] return examples def val_cells_not_empty(values) -> ValidationResult: title = "Not Empty" indices = [] for index, value in enumerate(values): if not value: indices.append(index) if not indices: score = 'Pass' explanation = 'All cells are non-blank' else: score = 'Error' explanation = 'Action Required. The following rows are blank:' return ValidationResult(score, title, explanation, indices) def get_row(index): return index + 2 cell_validation_functions = [globals()[name] for name in globals() if name.startswith('val_cells_')] if __name__ == "__main__": print(cell_validation_functions) PK\qOll rtm/fields/validation_results.pyimport click from typing import List class ValidationResult: def __init__(self, score, title, explanation=None, nonconforming_indices=None): self._scores_and_colors = {'Pass': 'green', 'Warning': 'yellow', 'Error': 'red'} self._set_score(score) self._title = title self._explanation = explanation self._set_indices(nonconforming_indices) def _set_indices(self, indices): if indices: self.indices = tuple(indices) else: self.indices = '' def _set_score(self, score) -> None: if score not in self._scores_and_colors: raise ValueError(f'{score} is an invalid score') self._score = score def _get_color(self) -> str: return self._scores_and_colors[self._score] def _get_rows(self) -> str: if not self.indices: return '' first_row = 2 # this is the row # directly after the headers return ' ' + str(index + first_row for index in self.indices) def print(self) -> None: # --- Print Score in Color ------------------------------------------------ click.secho(f"\t{self._score}", fg=self._get_color(), bold=True, nl=False) # --- Print Rule Title ---------------------------------------------------- click.secho(f"\t{self._title.upper()}", bold=True, nl=False) # --- Print Explanation (and Rows) ---------------------------------------- if self._explanation: click.secho(f' - {self._explanation}{self.indices}', nl=False) click.echo() # new line def print_validation_report(field_name, field_validation_results: List[ValidationResult]) -> None: print_val_header(field_name) for result in field_validation_results: result.print() def print_val_header(field_name) -> None: sym = '+' box_middle = f"{sym} {field_name} {sym}" box_horizontal = sym * len(box_middle) click.echo() click.secho(box_horizontal, bold=True) click.secho(box_middle, bold=True) click.secho(box_horizontal, bold=True) click.echo() PK=pO~%%rtm/validation/__init__.pyfrom .check_form_functions import * PK\qOyMyM&rtm/validation/check_form_functions.py""" This script serves as a library of functions for FDR checker. Each function has two comments before it, the first is which column it is intended for and the second states what it does. unless otherwise stated, each function returns a boolean true or false """ # functions start here # Any # take value in and return it with no carriage returns (\n). This will make strings easier to evaluate def remove_carriage_returns(value): value = value.replace("\n", "") return value # Any # check if empty. returns True if empty def is_empty(value): if not value: return True else: return False # Any # check if value is N/A. # FDR rules: type of requirement/other circumstances may/may not allow N/A in certain fc def is_notapplic(value): # remove whitespace for direct string comparison. e.g. 'n / a ' becomes 'n/a' value = value.replace(" ", "") # compare lower case version of cell contents to 'n/a'. if value.lower() == "n/a": return True else: return False # Any # check if value is explicitly a hyphen # FDR rules: if row is a procedure step, all columns besides ID, cascade visualizer, cascade level and requirement # statement should be a hyphen def is_hypen(value): # remove whitespace for direct string comparison. e.g. ' - ' becomes '-' value = value.replace(" ", "") if value == "-": return True else: return False # Any # check if value is yes def is_yes(value): # remove whitespace for direct string comparison. e.g. 'yes ' becomes 'yes' value = value.replace(" ", "") if value.lower() == "yes": return True else: return False # Any # check if value is no def is_no(value): # remove whitespace for direct string comparison. e.g. 'no ' becomes 'no' value = value.replace(" ", "") if value.lower() == "no": return True else: return False # Any # check if value contains 'not required' in its text # FDR rules: some fc are not required. e.g. validation is not required if requirement is a business need def has_not_required(value): if value.lower().find("not required") != -1: return True else: return False # ID # check that value has a capital P as the first character. # FDR rules: recommended ID formatting for procedure steps and procedure based requirements follow a naming convention. # e.g. P010, P020, etc. for procedure steps and P010-020 for procedure based requirements def starts_with_p(value): if value.startswith("P"): return True else: return False # ID # check if value has integers following the first letter # FDR rules: recommended ID formatting for procedure steps follow a naming convention. # e.g. P010, P020, etc. for procedure steps def has_digits_after_first(value): return value[1:].isdigit() # ID # check if value has 3 integers following the first character. First char is omitted # FDR rules: recommended ID formatting for procedure steps follow a naming convention. # e.g. P010, P020, etc. for procedure steps def has_three_digits(value): str1 = value[1:] if (len(str1) == 3) and (str1.isdigit() is True): return True else: return False # ID # check if value has 6 integers following the first character. # FDR rules: recommended ID formatting for procedure based requirements follow a naming convention. # e.g. P010-020, P010-030, etc. for procedure based requirements # NOTE: First char is omitted. Assumes there is a dash and removes it def has_six_digits(value): # slice string. keep all characters after the first. (removes P) value_slice = value[1:] # find the location/_get_index of the hypen within the string. dash_index = value_slice.find("-") # slice string around the hyphen. this will leave only the numeric characters if ID if formatted correctly value_slice = value_slice[:dash_index] + value_slice[dash_index + 1:] if (len(value_slice) == 6) and (value_slice.isdigit() is True): return True else: return False # ID # check for hyphen within string # FDR rules: recommended ID formatting for procedure based requirements follow a naming convention. # e.g. P010-020, P010-030, etc. for procedure based requirements def has_hyphen(value): if value.find("-") != -1: return True else: return False # ID # Check for dash in 4th position (P010-001) # FDR rules: recommended ID formatting for procedure based requirements follow a naming convention. # e.g. P010-020, P010-030, etc. for procedure based requirements def has_hyphen_positioned(value): if value.find("-") == 4: return True else: return False # Cascade Block # check for capital X # FDR rules: only a capital X or capital F are allowed in the cascade visualizer columns. (B-G in its current form) # TODO Question: "has" implies there are allowed to be other chars in the string as well. # TODO I forget how, but there's a better way of removing that whitespace. Strip, maybe? def has_capital_x(value): # remove whitespace for direct string comparison. e.g. ' X ' becomes 'X' value = value.replace(" ", "") if value == "X": return True else: return False # Cascade Block # check for lowercase x # FDR rules: only a capital X or capital F are allowed in the cascade visualizer columns. (B-G in its current form) def has_lower_x(value): # remove whitespace for direct string comparison. e.g. ' x ' becomes 'x' value = value.replace(" ", "") if value == "x": return True else: return False # Cascade Block # check for capital F # FDR rules: only a capital X or capital F are allowed in the cascade visualizer columns. (B-G in its current form) def has_capital_f(value): # remove whitespace for direct string comparison. e.g. ' F ' becomes 'F' value = value.replace(" ", "") if value == "F": return True else: return False # Cascade Block # check for lowercase f # FDR rules: only a capital X or capital F are allowed in the cascade visualizer columns. (B-G in its current form) def has_lower_f(value): # remove whitespace for direct string comparison. e.g. ' f ' becomes 'f' value = value.replace(" ", "") if value == "f": return True else: return False # Cascade level # check if cascade level is 'procedure step' # FDR rules: cascade level defines the type of requirement and can only contain one of the following strings: # procedure step, user need, risk need, business need, design input or design output def is_procedure_step(value): # remove whitespace at the beginning and end of the string and test for value if value.strip().lower() == "procedure step": return True else: return False # Cascade Level # check if cascade level is 'user need' # FDR rules: cascade level defines the type of requirement and can only contain one of the following strings: # procedure step, user need, risk need, business need, design input or design output def is_user_need(value): # remove whitespace at the beginning and end of the string and test for value if value.strip().lower() == "user need": return True else: return False # Cascade Level # check if cascade level is 'risk need' # FDR rules: cascade level defines the type of requirement and can only contain one of the following strings: # procedure step, user need, risk need, business need, design input or design output def is_risk_need(value): # remove whitespace at the beginning and end of the string and test for value if value.strip().lower() == "risk need": return True else: return False # Cascade Level # check if cascade level is 'business need' # FDR rules: cascade level defines the type of requirement and can only contain one of the following strings: # procedure step, user need, risk need, business need, design input or design output def is_business_need(value): # remove whitespace at the beginning and end of the string and test for value if value.strip().lower() == "business need": return True else: return False # Cascade Level # check if cascade level is 'design input' # FDR rules: cascade level defines the type of requirement and can only contain one of the following strings: # procedure step, user need, risk need, business need, design input or design output def is_design_input(value): # remove whitespace at the beginning and end of the string and test for value if value.strip().lower() == "design input": return True else: return False # Cascade Level # check if cascade level is 'design output solution' # FDR rules: cascade level defines the type of requirement and can only contain one of the following strings: # procedure step, user need, risk need, business need, design input or design output def is_design_output(value): # remove whitespace at the beginning and end of the string and test for value if value.strip().lower() == "design output solution": return True else: return False # Cascade level # check if cascade level is one of the approved options. # returns true if it is procedure step, user need, risk need, business need, design input or design output # FDR rules: cascade level may only be one of the 6 defined types. def is_cascade_lvl_approved(value): if is_procedure_step(value) \ ^ is_user_need(value) \ ^ is_risk_need(value) \ ^ is_business_need(value) \ ^ is_design_input(value) \ ^ is_design_output(value) is True: return True else: return False # V&V Results # check if W/C,wc or windchill is present. should indicate if windchill number is present # FDR rules: Design inputs and outputs may reference a document in windchill for its verification/validation results def has_w_slash_c(value): # convert input argument to all lower case for comparison val_lower = value.lower() if val_lower.find("w/c") != -1: return True elif val_lower.find("wc") != -1: return True elif val_lower.find("windchill") != -1: return True else: return False # V&V # check if 10 digit windchill number is present. example W/C# 0000006634 def is_windchill_number_present(value): # remove all spaces value = value.replace(" ", "") # find _get_index of 000. windchill numbers have at least three leading zeros. leading_zeros_index = value.find("000") # slice the string starting at that _get_index until the end of the string value = value[leading_zeros_index:] # slice string again into two parts. first 10 characters (possible WC number) and remaining characters wc_number = value[:9] remaining_char = value[10:] # test if wc_number is all digits and remaining is all letters if wc_number.isdigit() and (remaining_char.isalpha() or len(remaining_char) == 0) is True: return True else: return False # Design Output Feature # check for CTQ IDs. returns true if "CTQ" is present in the cell # FDR rules: CTQ (critical to quality) features should be called out in the Design Output features column. # CTQs should be called out using the following format: (CTQ08) def has_ctq_id(value): if value.lower().find("ctq") != -1: return True else: return False # Design Output Features # check for CTQ number after CTQ tag. returns true if all occurrences of CTQ are followed by two digits # returns false if no CTQs are present OR they are not followed by two digits. (this should be used in conjunction # with the previous function that looks for CTQ in the cell to eliminate possibility of the former case) # FDR rules: CTQ (critical to quality) features should be called out in the Design Output features column. # CTQs should be called out using the following format: (CTQ08) def has_ctq_numbers(value): ctq_count = 0 number_count = 0 # find _get_index of first CTQ ID ctq_index = value.lower().find("ctq") # while loop will keep searching for CTQ IDs until there are none. the string is sliced, checked for digits, # searched for a new ID, _get_index found for new CTQ ID, repeat. while ctq_index != -1: # add 1 to ctq_counter, if there were no CTQs, the while condition would not be met. ctq_count += 1 # slice value from after "ctq" value = value[ctq_index + 3:] # if the next two characters are numbers (they should be if formatted correctly) if value[0:2].isdigit() is True: # add 1 to number counter. this counter will be compared to ctq_count later. they should match number_count += 1 # search for next CTQ. if there are not, find() will output a -1 and while loop will end ctq_index = value.lower().find("ctq") # if "ctq" and number count match AND they aren't zero...they are formatted correctly. if (ctq_count == number_count) and ctq_count > 0: return True else: return False # Requirement Statement # checks for hash (#) symbol in string # FDR rules: hastags are used to identify parent/child relationships, # functional requirements, mating part requirements, user interface requirements and mechanical properties def has_hash(value): if value.find("#") != -1: return True else: return False # Requirement Statement # checks for #Function in cell. # FDR rules: The requirement statement can be tagged using #Function to identify a functional requirement def has_hash_function(value): if value.find("#Function") != -1: return True else: return False # Requirement Statement # checks for #MatingParts # FDR rules: The requirement statement can be tagged using #MatingParts to identify a requirement pertaining to proper # fitting between components def has_hash_mating_parts(value): if value.find("#MatingParts") != -1: return True else: return False # Requirement Statement # checks for #MechProperties # FDr rules: The requirement statement can be tagged using #MechProperties to identify a requirement that pertains to # the mechanical properties of the implant/instrument def has_hash_mech_properties(value): if value.find("#MechProperties") != -1: return True else: return False # Requirement Statement # checks for #UserInterface # FDR rules: the requirement statement can be tagged using #UserInterface to identify a requirement that relates to how # the user handles the implant/instrument def has_hash_user_interface(value): if value.find("#UserInterface") != -1: return True else: return False # TODO will #Parent or #AdditionalParent be used in requirement statement? sticking with #Parent for now # Requirement Statement # checks for #Child returns true if #Child is present # FDR rules: #Child and #Parent are used to link a Design Input that leads to a Design Output Solution that has # been documented earlier in the form. The Design Input is tagged using #Child = P###-### where the ID refers to the # Output solution and the Output solution is tagged using #Parent = P###-### where the ID refers to the Design Input def has_hash_child(value): if value.find("#Child") != -1: return True else: return False # Requirement Statement # checks for #Parent returns true if #Parent is present # FDR rules: #Child and #Parent are used to link a Design Input that leads to a Design Output Solution that has # been documented earlier in the form. The Design Input is tagged using #Child = P###-### where the ID refers to the # Output solution and the Output solution is tagged using #Parent = P###-### where the ID refers to the Design Input # TODO let's not use the word "hash" here. It has a very specific meaning in computer science which can cause # confusion. In fact, you've probably already heard/seen it mentioned in the git literature. It's synonymous with # "checksum". It's how Python dictionaries work too. def has_hash_parent(value): if value.find("#Parent") != -1: return True else: return False # Requirement Statement # returns IDs (P###-###) that are tagged using #Child as a list. assumes there are #Child present. # FDR rules: #Child and #Parent are used to link a Design Input that leads to a Design Output Solution that has # been documented earlier in the form. The Design Input is tagged using #Child = P###-### where the ID refers to the # Output solution and the Output solution is tagged using #Parent = P###-### where the ID refers to the Design Input def child_ids(value): # init output list. will append with values later ids_output_list = [] # remove spaces for easier evaluation value = value.replace(" ", "") # while there are #child in string. string will be sliced after each ID is retrieved while value.find("#Child") != -1: # find the _get_index of the child hashtag hash_index = value.find("#Child") value = value[hash_index:] # find the beginning of the ID by searching for P id_start_index = value.find("P") # append output list with ID ids_output_list.append(value[id_start_index:id_start_index + 7]) value = value[id_start_index:] return ids_output_list # Requirement Statement # returns IDs (P###-###) that are tagged using #Parent as a list. assumes there are #Parent present. # FDR rules: #Child and #Parent are used to link a Design Input that leads to a Design Output Solution that has # been documented earlier in the form. The Design Input is tagged using #Child = P###-### where the ID refers to the # Output solution and the Output solution is tagged using #Parent = P###-### where the ID refers to the Design Input def parent_ids(value): # init output list. will append with values later ids_output_list = [] # remove spaces for easier evaluation value = value.replace(" ", "") # while there are #child in string. string will be sliced after each ID is retrieved while value.find("#Parent") != -1: # find the _get_index of the child hashtag hash_index = value.find("#Parent") # slice value from the hash_index + 2 (to account for capital P at the beginning of Parent) to the end value = value[hash_index+2:] # find the beginning of the ID by searching for P id_start_index = value.find("P") # append output list with ID ids_output_list.append(value[id_start_index:id_start_index + 7]) value = value[id_start_index:] return ids_output_list #list of tags """ SANDBOX """ if __name__ == '__main__': # This is your playground # call function # print result testval = "blah blah TBD \n anatomy (TBD percentile, etc.). \nFunction #Parent = P40-030 #Parent = P40-040" testout = remove_carriage_returns(testval) print(testout) pass """ # init a mock requirement (row on FDR) for testing req1 = dict(iD="P20", procedureStep=" ", userNeed="X", cascadeLevel="DESIGN OUTPUT SOLUTION", requirementStatement="Prepare Patient") print("\n") print(req1) print("\n") """ PK=pO3e rtm/validation/headers_list.py""" this script initializes a list of "approved" column headers. It also defines a function that compares the approved list to an input list (future state will be from User's file) and returns a named tuple with found, not found and extra fc. """ """ #USE THIS!! list1 = [ 'item1', 'item2', ] """ def header_check(approved_headers_list,users_headers_list): # use set() to determine what is missing from users list (approved - users) missing_from_users = list(set(approved_headers_list)-set(users_headers_list)) # use set() to determine the extra items on users list (users - approved) users_extra = list(set(users_headers_list)-set(approved_headers_list)) if (missing_from_users and users_extra) == []: matches = users_headers_list """ SANDBOX """ if __name__ == '__main__': # approved_headers_list contains the "approved" column headers approved_headers_list = [] approved_headers_list.append("ID") approved_headers_list.append("Procedure Step") approved_headers_list.append("User Need") approved_headers_list.append("Design Input") approved_headers_list.append("DO Solution L1") approved_headers_list.append("DO Solution L2") approved_headers_list.append("DO Solution L3") approved_headers_list.append("Cascade Level") approved_headers_list.append("Requirement Statement") approved_headers_list.append("Requirement Rationale") approved_headers_list.append("Verification or Validation Strategy") approved_headers_list.append("Verification or Validation Results") approved_headers_list.append("Devices") approved_headers_list.append("Design Output Feature (with CTQ ID #)") approved_headers_list.append("CTQ? Yes, No, N/A") # print(approved_headers_list) # headers_list contains a sample users column headers users_headers_list = [] users_headers_list.append("ID") users_headers_list.append("Procedure Step") users_headers_list.append("User Need") users_headers_list.append("Design Input") users_headers_list.append("DO Solution L1") users_headers_list.append("DO Solution L2") users_headers_list.append("DO Solution L3") users_headers_list.append("Cascade Level") users_headers_list.append("Requirement Statement") users_headers_list.append("Requirement Rationale") users_headers_list.append("Verification or Validation Strategy") users_headers_list.append("Verification or Validation Results") users_headers_list.append("Devices") users_headers_list.append("Design Output Feature (with CTQ ID #)") users_headers_list.append("CTQ? Yes, No, N/A") users_headers_list.append("Extra Header1") # users_headers_list.append("Extra Header2") # print(users_headers_list) # This is your playground # call function # print result testout = header_check(approved_headers_list,users_headers_list) print(testout) pass PK=pOrtm/validation/junk.pyfrom rtm.fields.validation import cell_validation_functions if __name__ == "__main__": print(cell_validation_functions) PK=pO.LQQ'rtm/validation/some_random_functions.pyimport openpyxl import pathlib def is_integer(string): integers = str(1234567890) if string in integers: return True else: return False def get_first_integer_sequence(value): """Search through string, return first set of consecutive numbers as single integer :param value: :return: integer. `0` if no integer was found """ first_integer_set_found = False integer_string = '' output = -1 # default try: for char in value: if is_integer(char): # print(char) first_integer_set_found = True integer_string += char elif first_integer_set_found: break else: pass if integer_string != '': output = int(integer_string) finally: return output def convert_to_string_with_leading_zeroes(value, min_length=0) -> str: """ `84, 3` --> `084` `321, 1` --> `321` `hello, 3` --> `hello` :param value: :param min_length: :return: """ output = '0' * min_length # default output try: input_value = str(value) length = len(input_value) # print(f'length = {length}') missing_length = min_length - length if missing_length > 0: leading_zeroes = '0' * missing_length # print(f'leading zeroes = {leading_zeroes}') output = leading_zeroes + input_value else: output = input_value finally: return output PK!H$$)dps_rtm-0.1.13.dist-info/entry_points.txtN+I/N.,()**ɵbL<..PKBNuQQ dps_rtm-0.1.13.dist-info/LICENSEThe MIT License (MIT) Copyright (c) 2019 Jonathan Chukinas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HPOdps_rtm-0.1.13.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!HY>!dps_rtm-0.1.13.dist-info/METADATAW]s۶}ǯؑfj[#Rq;ͤ7JٕHHDM,ֿYGf"2^Fuڔc>>3Y1/vU| ꢐv320V$iU*{+EY5*ʍGYS2,~ԥtej{ٯO3qY1kwU]T!u>VDn7؉"~zC(X]y]#~S!W~YU.K͉ceUYV)?w˻p<uKS"I Q]bi.fTm*=Z3[vΌU&6vqo\kw_F!Xxd35ZJ|0-dRu9H}1qQ娱]g/|&&㒉Cw[;Q$,G`^Ñ.b!tm"gIZ0)o48 JVi"wzr#c 2ϩ /ce+.b?bT ӀC2@Ryz\Co(kR\h C~LwJ$&&Lh.Wtsi9v0ls=$dǦm!L CgT>1!5j[$X&7Z ~:W+vXM #"pΧwPo9L!OL͓_!=XH/kXL:M tz(hr*DŽ+-(& tbt3TKym۬c~j{^'xq j 5 `?e"+ˠJ| A|-5k]( %q@%Su~ $ XDGD6ؔAL'"TǃsW\(y^tՐ<~#b}u vw2 i̟2e1CqbD]bJЅ4 !Q@H:(e$O?8Ya| qmbeFPGm%!mvPp®G})p3%\1Ryf4PhI뙗n]|^ ϛu{UHn`!^kB96;roHIX/+5Tmj}HkJHtop $ԬXWo'@|W8ҥdz?]ݓw0a H˼\j6>wҪ ͧ;`%'K}zr<2W_gG؏櫬DpuE2 x(n v{moΕ*7kvs $c.bp:6l[ )6n .߶fIp7 *ݭ]@&8YQ%╒>HKipfo^Ue,}77oN,Ϊ b?}1]Ѕr#5)"5;_б.2t4=] GAJ5 岹&=gM2.o4mmB0D 4G6js BPK!HN*dps_rtm-0.1.13.dist-info/RECORD}ɶ8}<@I}߅˲֩νY}wPD(AG1u 2r#Xv#uwP.rqU qOR6y*o:ط,: R~d| on4-\}$frЄ+eq8?U 3']JdXo Q;dMϜğO JU\ckу2xAq ސԫ2}nyUPv8;Ҽ,D7p.b7w@B捽5(fDE'ӕuU{yǼS{z@mf8*wrF0l !Rvlm3Jy 9ڍ]/ek?U֙y]־sC ᜝2 7yd_ůQbGE#8^4lp Ĝ|~"c9o-ɚ'I> Oqs#~{5]|0W݃;el5tJz9>KW+v$ h ((P c|CS63m[fǰa?B8+SGeϧS^ɉC+nޮU(ww6eDLૈ$W_|AZ~w-V?MuGJdҬi9Eo `$c ^tIV+X{>7)`=z ??W񋬎Q=j~b'=|9Mh/Ǒm 6PkfFP lKW&u(6örm\jKc`8WvDA8~KYӗŬQb%{E4HY@"\O ]nyJJ e֓k䅻^T22=5 4?}> %PKtOx0JJrtm/__init__.pyPK=pO*^22 wrtm/api.pyPK=pO ZZ rtm/cli.pyPK\qO,Srtm/exceptions.pyPK=pO,D]33rtm/path_handling.pyPKsO rtm/rtm_worksheet.pyPK\qONrtm/worksheet_columns.pyPKsOrtm/fields/__init__.pyPKsODMrtm/fields/cascade_block.pyPK=pO* S  rtm/fields/field.pyPKsO_G&+rtm/fields/field_subclasses.pyPK\qOO3rtm/fields/validation.pyPK\qOll :rtm/fields/validation_results.pyPK=pO~%%FCrtm/validation/__init__.pyPK\qOyMyM&Crtm/validation/check_form_functions.pyPK=pO3e `rtm/validation/headers_list.pyPK=pO]rtm/validation/junk.pyPK=pO.LQQ'rtm/validation/some_random_functions.pyPK!H$$)dps_rtm-0.1.13.dist-info/entry_points.txtPKBNuQQ dps_rtm-0.1.13.dist-info/LICENSEPK!HPOdps_rtm-0.1.13.dist-info/WHEELPK!HY>!.dps_rtm-0.1.13.dist-info/METADATAPK!HN*dps_rtm-0.1.13.dist-info/RECORDPKr6