diff --git a/agent_based/inv_cisco_bug.py b/agent_based/inv_cisco_bug.py
index 65d869bdddf1bfec15b971a1bcb67169ed6de638..57a1f450a43e883530ee9890beaecb7dd21b2985 100644
--- a/agent_based/inv_cisco_bug.py
+++ b/agent_based/inv_cisco_bug.py
@@ -36,7 +36,7 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     InventoryResult,
 )
 
-from cmk.base.plugins.agent_based.utils.ciscoapi import (
+from cmk.base.plugins.agent_based.utils.inv_cisco_support import (
     set_loglevel,
     expand_path,
     get_base_path,
diff --git a/agent_based/inv_cisco_contract.py b/agent_based/inv_cisco_contract.py
index 52d543f02209b5451ef4db550f3f199def93ff57..280ac8de881fd15fe952cb3c5fdf575855f21438 100644
--- a/agent_based/inv_cisco_contract.py
+++ b/agent_based/inv_cisco_contract.py
@@ -19,12 +19,8 @@
 #             cleanup create_sn2info_record
 # 2021-07-24: rewritten for CMK 2.0
 # 2021-07-25: some cleanup
+# 2021-07-30: moved parse function to utils
 #
-#  ToDo: parse function and SNMP section can be shared between inv_cisco_eox and inv_cisco_contract
-#        pro: only one section/parse function
-#        con: with only one pare function these plugins can not disabled separately
-#        solution: separate SNMP section, common parse function
-# ToDo: error handling for json.load -> in eox/contract pass, in psirt/bug -> exit
 # ToDo: exit codes ??
 #
 import os
@@ -32,7 +28,6 @@ import json
 import time
 
 from typing import Dict
-from dataclasses import dataclass
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     register,
@@ -43,30 +38,27 @@ from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     TableRow,
 )
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    StringTable,
     InventoryResult,
 )
 
-from cmk.base.plugins.agent_based.utils.ciscoapi import (
+from cmk.base.plugins.agent_based.utils.inv_cisco_support import (
     expand_path,
-    check_bad_serial,
     pid_on_black_list,
     set_pid_black_list,
     get_base_path,
+    parse_inv_cisco_contract_eox,
+    SnmpContractEntry,
 )
 
 
-@dataclass
-class SnmpContractEntry:
-    phydescr: str
-    pid: str
-
-
 def _create_sn2info_record(sn2infofile, optional_columns):
     sn2info = {}
     if os.path.isfile(sn2infofile):
         with open(sn2infofile) as f:
-            sn2inforecord = json.load(f)
+            try:
+                sn2inforecord = json.load(f)
+            except ValueError as e:
+                return {'serial_number': f'json load error {e}'}
 
         modifytime = os.path.getmtime(sn2infofile)
         sn2info.update({'Last_checked': time.strftime('%Y-%m-%d', time.localtime(modifytime))})
@@ -95,31 +87,6 @@ def _create_sn2info_record(sn2infofile, optional_columns):
     return sn2info
 
 
-def parse_inv_cisco_contract(string_table: StringTable) -> Dict[str, SnmpContractEntry]:
-    serials: Dict[str, SnmpContractEntry] = {}
-    for phydescr, physerialnum, phymodelname in string_table:
-        if not check_bad_serial(physerialnum):
-            if not pid_on_black_list(phymodelname):
-                phymodelname = phymodelname.strip()
-                physerialnum = physerialnum.strip()
-                if phymodelname == '':
-                    phymodelname = 'UNSPECIFIED'
-                pid = phymodelname.split()[0].upper()
-                serial = physerialnum.split()[0].upper()
-                if serial not in serials.keys():
-                    serials.update({serial: SnmpContractEntry(
-                        phydescr=phydescr,
-                        pid=pid
-                    )})
-                else:
-                    temp = serials[serial]
-                    temp.phydescr = ', '.join(list(set(temp.phydescr.split(', ') + [phydescr])))
-                    temp.pid = ', '.join(list(set(temp.pid.split(', )') + [pid])))
-                    serials[serial] = temp
-
-    return serials
-
-
 def inventory_cisco_contract(params, section: Dict[str, SnmpContractEntry]) -> InventoryResult:
     optionalcolumns = [
         'contract_site_state_province',
@@ -178,7 +145,7 @@ def inventory_cisco_contract(params, section: Dict[str, SnmpContractEntry]) -> I
 
 register.snmp_section(
     name='inv_cisco_contract',
-    parse_function=parse_inv_cisco_contract,
+    parse_function=parse_inv_cisco_contract_eox,
     fetch=SNMPTree(
         base='.1.3.6.1.2.1.47.1.1.1.1',  # ENTITY-MIB::entPhysicalEntry
         oids=[
diff --git a/agent_based/inv_cisco_eox.py b/agent_based/inv_cisco_eox.py
index 6827dc1b52c4c6338a802cf89191d32e059457a0..58bcccfc668b294a9680e366021170c76256e685 100644
--- a/agent_based/inv_cisco_eox.py
+++ b/agent_based/inv_cisco_eox.py
@@ -19,13 +19,13 @@
 #             moved node tree from hardware.system.support to hardware.system
 # 2021-07-23: rewrite for CMK 2.0
 # 2021-07-26: cleanup
+# 2021-07-30: moved parse function to utils
 
 import os
 import json
 import time
 
 from typing import Dict
-from dataclasses import dataclass
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     register,
@@ -36,34 +36,30 @@ from cmk.base.plugins.agent_based.agent_based_api.v1 import (
     TableRow,
 )
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    StringTable,
     InventoryResult,
 )
 
-# from cmk.base.plugins.agent_based.utils import ciscoapi as utils
-from cmk.base.plugins.agent_based.utils.ciscoapi import (
+from cmk.base.plugins.agent_based.utils.inv_cisco_support import (
     expand_path,
-    check_bad_serial,
     pid_on_black_list,
     sn_on_black_list,
     pid_on_bad_list,
     set_pid_bad_list,
     set_sn_black_list,
     get_base_path,
+    parse_inv_cisco_contract_eox,
+    SnmpContractEntry,
 )
 
 
-@dataclass
-class SnmpContractEntry:
-    phydescr: str
-    pid: str
-
-
 def _create_eox_record(eoxfile, optional_columns):
     eox = {}
     if os.path.isfile(eoxfile):
         with open(eoxfile) as f:
-            eoxrecord = json.load(f)
+            try:
+                eoxrecord = json.load(f)
+            except ValueError as e:
+                return {}
 
         modifytime = os.path.getmtime(eoxfile)
 
@@ -104,31 +100,6 @@ def _create_eox_record(eoxfile, optional_columns):
     return eox
 
 
-def parse_inv_cisco_eox(string_table: StringTable) -> Dict[str, SnmpContractEntry]:
-    serials: Dict[str, SnmpContractEntry] = {}
-    for phydescr, physerialnum, phymodelname in string_table:
-        if not check_bad_serial(physerialnum):
-            if not pid_on_black_list(phymodelname):
-                phymodelname = phymodelname.strip()
-                physerialnum = physerialnum.strip()
-                if phymodelname == '':
-                    phymodelname = 'UNSPECIFIED'
-                pid = phymodelname.split()[0].upper()
-                serial = physerialnum.split()[0].upper()
-                if serial not in serials.keys():
-                    serials.update({serial: SnmpContractEntry(
-                        phydescr=phydescr,
-                        pid=pid
-                    )})
-                else:
-                    temp = serials[serial]
-                    temp.phydescr = ', '.join(list(set(temp.phydescr.split(', ') + [phydescr])))
-                    temp.pid = ', '.join(list(set(temp.pid.split(', )') + [pid])))
-                    serials[serial] = temp
-
-    return serials
-
-
 def inventory_cisco_eox(params, section: Dict[str, SnmpContractEntry]) -> InventoryResult:
     always_use_serial = False
     optionalcolumns = ['EndOfSecurityVulSupportDate',
@@ -268,7 +239,7 @@ def inventory_cisco_eox(params, section: Dict[str, SnmpContractEntry]) -> Invent
 
 register.snmp_section(
     name='inv_cisco_eox',
-    parse_function=parse_inv_cisco_eox,
+    parse_function=parse_inv_cisco_contract_eox,
     fetch=SNMPTree(
         base='.1.3.6.1.2.1.47.1.1.1.1',  # ENTITY-MIB::entPhysicalEntry
         oids=[
diff --git a/agent_based/inv_cisco_psirt.py b/agent_based/inv_cisco_psirt.py
index 26955bd8d2b9453b74fc332a12db5237ba1ffff4..634831546fbc5e3a000f320a11d6e8402f343e09 100644
--- a/agent_based/inv_cisco_psirt.py
+++ b/agent_based/inv_cisco_psirt.py
@@ -44,7 +44,7 @@ from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
     InventoryResult,
 )
 
-from cmk.base.plugins.agent_based.utils.ciscoapi import (
+from cmk.base.plugins.agent_based.utils.inv_cisco_support import (
     expand_path,
     get_base_path,
 )
@@ -61,7 +61,11 @@ def _create_psirt_record(filepath, filename, not_updated, dont_show_older_then,
     advisories = {}
     if os.path.isfile(psirtfile):
         with open(psirtfile) as f:
-            psirtrecord = json.load(f)
+            try:
+                psirtrecord = json.load(f)
+            except ValueError as e:
+                    exit()
+
         advisories = psirtrecord.get('advisories')
         remove_advisories = []
         for advisory in advisories:
diff --git a/agent_based/utils/inv_cisco_support.py b/agent_based/utils/inv_cisco_support.py
new file mode 100644
index 0000000000000000000000000000000000000000..958bf73a04211b8eea4d8bdd90b22db770dc63e2
--- /dev/null
+++ b/agent_based/utils/inv_cisco_support.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2017-03-20
+#
+# include file, will be used with snmp_cisco_eox and snmp_cisco_contract
+#
+# 2017-05-29: fixed empty pid handling
+#             added serial number cleanup
+# 2021-07-23: rewrite for CMK 2.0
+# 2021-07-30: renamed form ciscoapi.py to inv_cisco_support.py
+
+import os
+import logging
+import re
+import json
+from typing import List, Dict
+from dataclasses import dataclass
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    StringTable,
+)
+
+
+@dataclass
+class SnmpContractEntry:
+    phydescr: str
+    pid: str
+
+#
+# global variables
+#
+# list of PIDs to drop
+g_PID_black_list = ['BUILT-IN', 'MICRON', 'C400-MTFDD']
+# list of PIDs to try by serial number
+g_PID_bad_list = ['UNSPECIFIED', 'FABRIC', 'ASA', 'C2611XM-2FE', 'FTLX8570D3BCL', 'FTLF8519P2BCL', 'FTLX8571D3BCL',
+                  'FTRJ-8519-7D', 'PLRXPL-SC-S43']  #
+# list of S/Ns to drop
+g_SN_black_list = []
+
+
+def parse_inv_cisco_contract_eox(string_table: StringTable) -> Dict[str, SnmpContractEntry]:
+    serials: Dict[str, SnmpContractEntry] = {}
+    for phydescr, physerialnum, phymodelname in string_table:
+        if not check_bad_serial(physerialnum):
+            if not pid_on_black_list(phymodelname):
+                phymodelname = phymodelname.strip()
+                physerialnum = physerialnum.strip()
+                if phymodelname == '':
+                    phymodelname = 'UNSPECIFIED'
+                pid = phymodelname.split()[0].upper()
+                serial = physerialnum.split()[0].upper()
+                if serial not in serials.keys():
+                    serials.update({serial: SnmpContractEntry(
+                        phydescr=phydescr,
+                        pid=pid
+                    )})
+                else:
+                    temp = serials[serial]
+                    temp.phydescr = ', '.join(list(set(temp.phydescr.split(', ') + [phydescr])))
+                    temp.pid = ', '.join(list(set(temp.pid.split(', )') + [pid])))
+                    serials[serial] = temp
+
+    return serials
+
+
+def get_base_path() -> str:
+    conf_file = os.path.expanduser('~/etc/ciscoapi/ciscoapi.json')
+    base_path = '~/var/ciscoapi'
+    # check for conf_file and read parameters
+    if os.path.isfile(conf_file):
+        with open(conf_file) as f:
+            try:
+                config = json.load(f)
+                base_path = config['global'].get('base_dir', base_path)
+            except ValueError as e:
+                logging.warning(f'inv_cisco_contract:status:JSON load error: {e}')
+    return base_path
+
+
+def set_pid_black_list(pid_black_list: List[str]):
+    global g_PID_black_list
+    if pid_black_list:
+        g_PID_black_list = list(set(g_PID_black_list + pid_black_list))
+
+
+def set_pid_bad_list(pid_bad_list: List[str]):
+    global g_PID_bad_list
+    if pid_bad_list:
+        g_PID_bad_list = list(set(g_PID_bad_list + pid_bad_list))
+
+
+def set_sn_black_list(sn_black_list: List[str]):
+    global g_SN_black_list
+    if sn_black_list:
+        g_SN_black_list = list(set(g_SN_black_list + sn_black_list))
+
+
+def check_dir_and_create(directory: str) -> bool:
+    """
+    check if dir exists, if not try to create it.
+
+    Args:
+        directory:
+
+    Returns:
+        True: if dir exists or creation was ok
+        False: if dir not exists and creation was not ok
+    """
+    directory = os.path.dirname(directory)
+    if not os.path.exists(directory):
+        try:
+            os.makedirs(directory)
+        except:
+            return False
+    return True
+
+
+def expand_path(path: str) -> str:
+    """
+    expand user dir and add '/' if necessary and create directory if it not exists
+    """
+    homedir = os.path.expanduser('~')
+    if path.startswith('~'):
+        path = homedir + path[1:]
+    if not path.endswith('/'):
+        path += '/'
+    if not check_dir_and_create(path):
+        return ''
+    return path
+
+
+def sn_on_black_list(serial: str) -> bool:
+    """
+    Checks if serial on black list
+
+    Args:
+        serial: the serial number to check
+
+    Returns:
+        True: if serial number on black list
+        False: if serial number is not on black list
+    """
+    global g_SN_black_list
+    if serial.upper() in g_SN_black_list:
+        return True
+    return False
+
+
+def pid_on_black_list(pid: str) -> bool:
+    """
+    returns True if pid on black list
+    """
+    global g_PID_black_list
+    for drop_PID in g_PID_black_list:
+        if pid.startswith(drop_PID.upper()):
+            return True
+    # if PID not on Black list return false
+    return False
+
+
+def pid_on_bad_list(pid: str) -> bool:
+    """
+    returns True if PID on Bad list
+    """
+    global g_PID_bad_list
+    # remove all chars from string, except allowedchars
+    allowedchars = re.compile('[^A-Z0-9_=\/\-\+\.\\\]')
+    cleanpid = allowedchars.sub('', pid).strip().upper()
+
+    # if PID contains illegal signs or if pid empty try by serial number
+    if (cleanpid != pid) or (cleanpid == ''):
+        return True
+
+    # list of bad PIDs :-( we need to try get EoX info via serial number for this PIDs
+    # needs to be configurable via a file if too big for wato ;-(
+    for bad_PID in g_PID_bad_list:
+        if pid.startswith(bad_PID):
+            return True
+
+    return False
+
+
+def check_bad_serial(serial: str) -> bool:
+    """
+    Checks a serial number for illegal signs (anything except A-Z0-9_=-+.\)
+
+    Args:
+        serial: serial number to check
+
+    Returns:
+        False: if serial number is ok
+        True: if serial number contains bad signs
+    """
+    logging.info('Check_bad_serial:serial: %s' % serial)
+    serial = serial.replace(' ', '').upper()
+
+    # remove all chars from string, except allowedchars
+    allowedchars = re.compile('[^A-Z0-9_=\-\+\.\\\]')
+    cleanserial = allowedchars.sub('', serial).strip()
+
+    logging.info('Check_bad_serial:cleanserial: %s' % cleanserial)
+
+    # if serial contains illegal signs or empty return true
+    if (cleanserial != serial) or (cleanserial == ''):
+        logging.info('Check_bad_serial:bad:serial is bad')
+
+        return True
+
+    logging.info('Check_bad_serial:serial is god')
+    return False
+
+
+def set_loglevel():
+    # set default logglevel
+    logging.basicConfig(level=logging.WARNING)
+    # has no effect as long previous command is active (is by design)
+    logging.basicConfig(level=logging.INFO)
diff --git a/bin/ciscoapi/cisco-bug.py b/bin/ciscoapi/cisco-bug.py
index c0491bdfc27c34264d3ddbc5b60072ee90f5af04..9ddbb7fa6fbefb9598632f45e6d87b70d189dde5 100755
--- a/bin/ciscoapi/cisco-bug.py
+++ b/bin/ciscoapi/cisco-bug.py
@@ -249,122 +249,6 @@ def main():
         else:
             reqoptions = ''
 
-        # pids_requested = {
-        #     # 'ISR4431/K9': '03.16.05.S,03.16.04b.S',  # 03.16.05.S,03.16.04b.S
-        #     # 'ISR-AP1100AC-E': '8.5.110.0',
-        #     #
-        #     # 'WS-X4013+TS': '12.2(25)SG4',  # --> not in database
-        #     # 'WS-C3750G-24TS-1U': '12.2(55)SE8',  # WS-C3750G-24TS-E1U --> wrong PID from inventory??
-        #     #
-        #     # 'C1861W-UC-2BRI-K9': '15.1(4)M12a',  # pid not in database (only 1861 and 1861E not W...)
-        #     # 'C6800IA-48TD': '15.0(2)EX7',
-        #     # 'C6880-X-LE-16P10G': '15.1(2)SY4a',
-        #     # 'C6880-X-LE-SUP': '15.1(2)SY4a',
-        #     # 'CISCO2801': '15.1(4)M12a',
-        #     # 'CISCO891-K9': '15.3(3)M3,15.3(3)M2',
-        #     # 'WS-C2960S-F24TS-L': '15.0(2)SE10a,15.0(2)SE6',
-        #     # 'WS-C2960S-F48TS-L': '15.0(2)SE10a,15.0(2)SE11,15.0(2)SE6',
-        #     # 'CISCO888-K9': '15.1(4)M4,15.1(4)M5,15.3(3)M4',
-        #     # 'WS-C2960G-24TC-L': '12.2(55)SE1,12.2(44)SE6',
-        #     # 'CISCO881-K9': '15.2(4)M6a,15.4(3)M6a',
-        #     # 'CISCO871-K9': '12.4(24)T8,12.4(15)T12,15.1(4)M8',
-        #     # 'WS-C2960PD-8TT-L': '15.0(2)SE6,12.2(55)SE12,15.0(2)SE10a',
-        #     #
-        #     # 'N3K-C3064PQ-10GX': '7.0(3)I4(4)',
-        #     # 'N5K-C5548UP': '7.0(8)N1(1)',
-        #     # 'N5K-C5596UP': '7.3(2)N1(1)',
-        #     # 'N5K-C56128P': '7.3(0)N1(1)',
-        #     # 'N5K-C5672UP': '7.0(8)N1(1)',
-        #     #
-        #     # 'CISCO3845-MB': '12.4(15)T13',
-        #     # 'WS-C2960-24TC-L': '12.2(44)SE6',
-        #     # 'WS-C3750-24PS-S': '12.2(35)SE5',
-        #     # 'WS-C3750G-12S-E': '12.2(55)SE8',
-        #     # 'WS-C3750G-24PS-S': '12.2(55)SE1',
-        #     #
-        #     # 'N9K-C93180YC-EX': '13.2(2o)',
-        #     # 'N9K-C9336PQ': '13.2(2o)',
-        #     #
-        #     # 'APIC-SERVER-M2': '3.2(2o)',
-        #     # 'C891F-K9': '15.3(3)M4',
-        #     # 'CISCO1941/K9': '15.0(1)M7',
-        #     # 'CISCO876W-G-E-K9': '15.1(4)M10',
-        #     # 'CISCO881-SEC-K9': '15.2(4)M6a',
-        #     # 'CISCO892-K9': '15.1(4)M5',
-        #     # 'WS-C2960X-24TD-L': '15.2(2)E3',
-        #     # 'WS-C2960X-24TS-LL': '15.2(2)E5',
-        #     # 'WS-C2960X-48FPD-L': '15.0(2)EX5',
-        #     # 'WS-C2960X-48FPS-L': '15.0(2)EX5',
-        #     # 'WS-C2960X-48LPD-L': '15.2(2)E3',
-        #     # 'WS-C2960X-48TS-LL': '15.2(6)E',
-        #     # 'WS-C3560CG-8TC-S': '15.2(2)E8',
-        #     # 'WS-C3750G-48TS-S': '15.0(2)SE6',
-        #     # 'WS-SUP720-3B': '15.1(2)SY2',
-        #     #
-        #     # 'WS-C3850-12S': '03.03.04SE',  # 3.3(4)SE
-        #     # 'WS-C3650-24TS-S': '03.07.02E',  # 3.7(2)E
-        #     # 'WS-C3650-48TQ': '03.03.05SE',  # 3.3(5)SE
-        #     #
-        #     # 'C9500-40X': '16.8.1a',
-        #     # 'C1117-4PMLTEEAWE': '16.09.01',
-        #     # 'ISR4351/K9': '16.09.01',
-        #     # 'ASR1001-X': '16.05.01b',
-        #     # 'ISR4451-X/K9': '16.05.01b',
-        #     # 'C9300-48T': '16.06.03',
-        #     #
-        #     # 'ASA5505': '8.4(4)1',
-        #     # 'ASA5506W': '9.9(2)1',
-        #     # 'ASA5508': '9.6(1)5,9.6(1)10',
-        #     # 'ASA5510': '9.1(6)1,8.4(7)30,9.1(7)15',
-        #     'ASA5512': '9.6(1),9.2(2)4,9.6(3)8',
-        #     # 'ASA5515': '9.8(1)5,9.9(2)14',
-        #     # 'ASA5520': '9.1(6)11',
-        #     # 'ASA5525': '9.8(1)',
-        #     # 'ASA5550': '9.1(5)',
-        #     # 'ASA5555': '9.9(1)2,9.7(1)4',
-        #     # 'PIX-515E': '8.0(4)32',  # pix is not in database
-        #     #
-        #     # 'AIR-CT5520-K9': '8.0(152.0),8.2(166.0),8.2(100.0)',
-        #     # 'AIR-AP1852E-E-K9': '8.2.100.0',
-        #     # 'AIR-CT2504-K9': '8.5.120.0',
-        # }
-
-
-
-        #
-        # try to get product series and get bugs by product series --> does not realy work :-(
-        #
-        #  product_series_mdf = ciscoapi.get_product_mdf_information_by_pid(pids_requested.keys(), access_token)
-        # pid_product_series = {}
-        # for entry in product_series_mdf:
-        #     pid_product_series.update({entry.get('product_id'):entry.get('product_series')})
-        #
-        # product_series_release = {}
-        # for pid in pid_product_series.keys():
-        #     if pid_product_series.get(pid) != '':
-        #         if product_series_release.get(pid_product_series.get(pid), '') == '':
-        #             product_series_release.update({pid_product_series.get(pid): pids_requested.get(pid)})
-        #         else:
-        #             release = product_series_release.get(pid_product_series.get(pid)) + ',' + pids_requested.get(pid)
-        #             product_series_release.update({pid_product_series.get(pid): release})
-        #
-        # for product_series in product_series_release.keys():
-        #     release = product_series_release.get(product_series)
-        #     release = release.split(',')
-        #     release_clean = []
-        #     for entry in release:
-        #         if entry not in release_clean:
-        #             release_clean.append(entry)
-        #     product_series_release.update({product_series: ','.join(release_clean)})
-
-        # print 'get bugs by product series and release'
-        # bugs = ciscoapi.get_bug_by_productseries_and_affected_release(product_series_release, access_token, reqoptions)
-        #
-        # end product series
-        #
-
-        # print 'get bugs by PID and release'
-
         for pid in pids_requested.keys():
             if lifetime > int(time.time()):
                 access_token, lifetime = get_new_token()
diff --git a/bin/ciscoapi/cisco-psirt.py b/bin/ciscoapi/cisco-psirt.py
index e60eee05eaacdb82fc290cd054dd560e191d6b0f..cc1caa6b7dd74aba4661a6fe76a4fef63de48155 100755
--- a/bin/ciscoapi/cisco-psirt.py
+++ b/bin/ciscoapi/cisco-psirt.py
@@ -12,39 +12,66 @@
 #
 #
 import logging
+import ntpath
 import os
 import json
 import time
 import ciscosupport
 import ciscoapi
 import sys
+from typing import List
+from dataclasses import dataclass
 
 
-def psirt_remove_id_file(psirt_path, psirt_id):
-    # set logg modul name <file>:<module>.<function>
+@dataclass
+class Paths:
+    found: str
+    not_found: str
+    request: str
+
+
+@dataclass
+class Refresh:
+    found: int
+    not_found: int
+
+
+g_logger = None
+
+
+def _psirt_remove_id_file(psirt_path: str, psirt_id: str):
+    # set logg module name <file>:<module>.<function>
     logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name)
 
     # delete psirt file
     try:
-        logger.debug('delete psirt id file : %s' % psirt_path + psirt_id)
+        logger.debug(f'delete psirt id file : {psirt_path + psirt_id}')
         os.remove(psirt_path + psirt_id)
     except OSError:
         pass
 
 
-def psirt_dump_record(psirt_record, psirt_id, psirt_path, psirt_path_request):
+def _psirt_dump_record(psirt_record, psirt_id: str, psirt_path: str, psirt_path_request: str):
     # set logg modul name <file>:<module>.<function>
     logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name)
 
     with open(psirt_path + psirt_id, 'w') as f:
         json.dump(psirt_record, f)
     # delete request file
-    psirt_remove_id_file(psirt_path_request, psirt_id)
+    _psirt_remove_id_file(psirt_path_request, psirt_id)
 
     return
 
 
-def check_psirt_record(psirt_record, psirt_id, psirt_path_found, psirt_path_request):
+def _check_psirt_record(psirt_record, psirt_id, psirt_path_found, psirt_path_request):
+    """
+
+    :param psirt_record:
+    :param psirt_id:
+    :param psirt_path_found:
+    :param psirt_path_request:
+    :returns:
+    """
     # set logg modul name <file>:<module>.<function>
     logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name)
 
@@ -61,53 +88,64 @@ def check_psirt_record(psirt_record, psirt_id, psirt_path_found, psirt_path_requ
             if advisory.get(key, None) in [['NA'], 'NA']:
                 advisory.pop(key, None)
 
-    psirt_dump_record(psirt_record, psirt_id, psirt_path_found, psirt_path_request)
+    _psirt_dump_record(psirt_record, psirt_id, psirt_path_found, psirt_path_request)
+
+    return
+
 
+def _get_psirt_id_list(product_family: str, paths: Paths, refresh: Refresh) -> List[str]:
+    """
+
+    @param product_family:
+    @param paths: Path object with path to founnd/not found/requested PSIRT records
+    @param refresh: Refresh object with number of days before a PSIRT record needs to be refreshed for found/not found
+    @return: list of PIDs
+    """
+    # create list of ID's to request PSIRT status for
+    psirt_id_list = ciscosupport.get_ids_from_dir(paths.request + product_family)
+    g_logger.debug(f'psirt requests : {psirt_id_list}')
+    # remove already found ID's from list
+    psirt_id_list = ciscosupport.remove_ids_from_list(psirt_id_list, paths.found + product_family)
+    g_logger.debug(f'psirt requests : {psirt_id_list}')
+    # remove not found ID's from list
+    psirt_id_list = ciscosupport.remove_ids_from_list(psirt_id_list, paths.not_found + product_family)
+    g_logger.debug(f'psirt requests : {psirt_id_list}')
+
+    # refresh psirt after 1 day by default
+    psirt_id_list = ciscosupport.refresh_ids_from_dir(paths.not_found + product_family, refresh.not_found,
+                                                      psirt_id_list, True)
+    g_logger.debug(f'psirt requests : {psirt_id_list}')
+    psirt_id_list = ciscosupport.refresh_ids_from_dir(paths.found + product_family, refresh.found,
+                                                      psirt_id_list, False)
+    g_logger.debug(f'psirt requests : {psirt_id_list}')
+
+    return psirt_id_list
+
+
+def _update_psirt_id(psirt_records: list, family_name: str, paths: Paths):
+    for psirt_record in psirt_records:
+        if family_name in ['IOS', 'IOS-XE']:
+            psirt_id = psirt_record.get('version')
+        else:
+            psirt_id = psirt_record.get('family')
+
+        if psirt_record.get('advisories') != 'notfound':
+            _check_psirt_record(psirt_record, psirt_id, paths.found + family_name + '/',
+                                ntpath.sep + family_name + '/')
+        else:
+            _psirt_dump_record(psirt_record, psirt_id, paths.not_found + family_name + '/',
+                               paths.request + family_name + '/')
+            # remove psirt_found file (happens when product family is removed form bug ID)
+            _psirt_remove_id_file(paths.found + family_name + '/', psirt_id)
     return
 
 
 def main():
-    def get_psirt_id_list(product_family):
-        # create list of ID's to request PSIRT status for
-        psirt_id_list = ciscosupport.get_ids_from_dir(path_request + product_family)
-        logger.debug('psirt requests : %s' % psirt_id_list)
-        # remove already found ID's from list
-        psirt_id_list = ciscosupport.remove_ids_from_list(psirt_id_list, path_found + product_family)
-        logger.debug('psirt requests : %s' % psirt_id_list)
-        # remove not found ID's from list
-        psirt_id_list = ciscosupport.remove_ids_from_list(psirt_id_list, path_not_found + product_family)
-        logger.debug('psirt requests : %s' % psirt_id_list)
-
-        # refresh psirt after 1 day by default
-        psirt_id_list = ciscosupport.refresh_ids_from_dir(path_not_found + product_family, psirt_refresh_notfound,
-                                                          psirt_id_list, True)
-        logger.debug('psirt requests : %s' % psirt_id_list)
-        psirt_id_list = ciscosupport.refresh_ids_from_dir(path_found + product_family, psirt_refresh_found,
-                                                          psirt_id_list, False)
-        logger.debug('psirt requests : %s' % psirt_id_list)
-
-        return psirt_id_list
-
-    def update_psirt_id(psirt_records, family_name):
-        for psirt_record in psirt_records:
-            if family_name in ['IOS', 'IOS-XE']:
-                psirt_id = psirt_record.get('version')
-            else:
-                psirt_id = psirt_record.get('family')
-
-            if psirt_record.get('advisories') != 'notfound':
-                check_psirt_record(psirt_record, psirt_id, path_found + family_name + '/',
-                                   path_request + family_name + '/')
-            else:
-                psirt_dump_record(psirt_record, psirt_id, path_not_found + family_name + '/',
-                                  path_request + family_name + '/')
-                # remove psirt_found file (happens when product family is removed form bug ID)
-                psirt_remove_id_file(path_found + family_name + '/', psirt_id)
-        return
+    global g_logger
 
     ciscosupport.set_logging('debug')
     # set logg modul name <file>:<module>.<function>
-    logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name)
+    g_logger = logging.getLogger(__file__ + ':' + __name__ + '.' + sys._getframe().f_code.co_name)
 
     conf_file = '~/etc/ciscoapi/ciscoapi.json'
     conf_file = os.path.expanduser(conf_file)
@@ -136,30 +174,34 @@ def main():
                 logging.warning(f'snmp_cisco_eox:status:JSON load error: {e}')
                 exit()
 
-        wait_after_start = config['global'].get('base_path', base_path)
+        base_path = config['global'].get('base_path', base_path)
         wait_after_start = config['global'].get('wait_after_start', wait_after_start)
         max_wait_time = config['global'].get('max_wait_time', max_wait_time)
         loglevel = config['global'].get('loglevel', loglevel)
-        psirt_refresh_found = config['psirt'].get('psirt_refresh_found', psirt_refresh_found)
-        psirt_refresh_notfound = config['psirt'].get('psirt_refresh_notfound', psirt_refresh_notfound)
+        refresh = Refresh(
+            found=config['psirt'].get('psirt_refresh_found', psirt_refresh_found),
+            not_found=config['psirt'].get('psirt_refresh_notfound', psirt_refresh_notfound)
+        )
     else:
-        logger.critical('Config file not found (%s).' % conf_file)
+        g_logger.critical(f'Config file not found ({conf_file}).')
         return False
 
     ciscosupport.set_logging(loglevel)
 
     base_path = ciscosupport.expand_path(base_path)
     psirt_dir = base_path + 'psirt'
-    path_found = psirt_dir + '/found/'
-    path_not_found = psirt_dir + '/not_found/'
-    path_request = psirt_dir + '/request/'
+    paths = Paths(
+        found=psirt_dir + '/found/',
+        not_found= psirt_dir + '/not_found/',
+        request=psirt_dir + '/request/'
+    )
 
-    psirt_ios = get_psirt_id_list('IOS')
-    psirt_ios_xe = get_psirt_id_list('IOS-XE')
-    psirt_family = get_psirt_id_list('family')
+    psirt_ios = _get_psirt_id_list('IOS', paths, refresh)
+    psirt_ios_xe = _get_psirt_id_list('IOS-XE', paths, refresh)
+    psirt_family = _get_psirt_id_list('family', paths, refresh)
 
     if (psirt_ios == []) and psirt_ios_xe == [] and psirt_family == []:
-        logger.debug('all list are empty. Do nothing.')
+        g_logger.debug('all list are empty. Do nothing.')
         return
 
     # wait random time after startup
@@ -178,20 +220,20 @@ def main():
     lifetime = starttime + lifetime - 30
 
     if access_token == '':
-        logger.critical('failed to get access_token')
+        g_logger.critical('failed to get access_token')
         return
 
     if psirt_family != []:
         psirt_records = ciscoapi.get_psirt_by_product_family(psirt_family, access_token)
-        update_psirt_id(psirt_records, 'family')
+        _update_psirt_id(psirt_records, 'family', paths)
 
     if psirt_ios_xe != []:
         psirt_records = ciscoapi.get_psirt_by_iosxe_version(psirt_ios_xe, access_token)
-        update_psirt_id(psirt_records, 'IOS-XE')
+        _update_psirt_id(psirt_records, 'IOS-XE', paths)
 
     if psirt_ios != []:
         psirt_records = ciscoapi.get_psirt_by_ios_version(psirt_ios, access_token)
-        update_psirt_id(psirt_records, 'IOS')
+        _update_psirt_id(psirt_records, 'IOS', paths)
 
 
 main()
diff --git a/inv_cisco_support.mkp b/inv_cisco_support.mkp
index cdf4854337fad3c970f66c317fac6bc40f6cca77..8e7dc469ba328e35138cc5aee143c978dfc4277a 100644
Binary files a/inv_cisco_support.mkp and b/inv_cisco_support.mkp differ
diff --git a/packages/inv_cisco_support b/packages/inv_cisco_support
index d374c2a88adf86de36249eab93235f80db59174e..16e48817894a1ceeeb5aff9364e5dcbb58ce4ad6 100644
--- a/packages/inv_cisco_support
+++ b/packages/inv_cisco_support
@@ -17,9 +17,9 @@
  'download_url': 'https://thl-cmk.hopto.org',
  'files': {'agent_based': ['inv_cisco_eox.py',
                            'inv_cisco_contract.py',
-                           'utils/ciscoapi.py',
                            'inv_cisco_bug.py',
-                           'inv_cisco_psirt.py'],
+                           'inv_cisco_psirt.py',
+                           'utils/inv_cisco_support.py'],
            'bin': ['ciscoapi/ciscoapi.py',
                    'ciscoapi/ciscosupport.py',
                    'ciscoapi/cisco-eox.py',
@@ -36,7 +36,7 @@
  'num_files': 17,
  'title': 'Inventory for Cisco Bug, EoX, contract status, PSIRT advisories and '
           'suggested software',
- 'version': '2021-07-25.v0.1a',
+ 'version': '2021-07-20.v0.1b',
  'version.min_required': '2.0.0',
  'version.packaged': '2021.07.14',
  'version.usable_until': None}
\ No newline at end of file
diff --git a/web/plugins/views/inv_cisco_support.py b/web/plugins/views/inv_cisco_support.py
index 01cfe890c8e466f298f2535ee0b558f4c27e7027..f3941393cc2bc80962e2bd7a6453b68d3799c037 100644
--- a/web/plugins/views/inv_cisco_support.py
+++ b/web/plugins/views/inv_cisco_support.py
@@ -127,7 +127,8 @@ def inv_paint_psirt_bugid(bugids):
         search_bugid_url = []
         for bugid in bugids:
             bugid = bugid.strip(' ')
-            search_bugid_url.append(f'<a class="href_blue" target="_blank" href="https://bst.cloudapps.cisco.com/bugsearch/bug/{bugid}">{bugid}</a>')
+            search_bugid_url.append(f'<a class="href_blue" target="_blank" '
+                                    f'href="https://bst.cloudapps.cisco.com/bugsearch/bug/{bugid}">{bugid}</a>')
         search_bugid_url = HTML(', '.join(search_bugid_url))
     return '', search_bugid_url