medicafe 0.240419.2__py3-none-any.whl → 0.240613.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of medicafe might be problematic. Click here for more details.
- MediBot/MediBot.bat +174 -38
- MediBot/MediBot.py +80 -77
- MediBot/MediBot_Charges.py +0 -28
- MediBot/MediBot_Crosswalk_Library.py +281 -0
- MediBot/MediBot_Post.py +0 -0
- MediBot/MediBot_Preprocessor.py +138 -211
- MediBot/MediBot_Preprocessor_lib.py +496 -0
- MediBot/MediBot_UI.py +80 -35
- MediBot/MediBot_dataformat_library.py +79 -35
- MediBot/MediBot_docx_decoder.py +295 -0
- MediBot/update_medicafe.py +46 -8
- MediLink/MediLink.py +207 -108
- MediLink/MediLink_837p_encoder.py +299 -214
- MediLink/MediLink_837p_encoder_library.py +445 -245
- MediLink/MediLink_API_v2.py +174 -0
- MediLink/MediLink_APIs.py +139 -0
- MediLink/MediLink_ConfigLoader.py +44 -32
- MediLink/MediLink_DataMgmt.py +297 -89
- MediLink/MediLink_Decoder.py +63 -0
- MediLink/MediLink_Down.py +73 -102
- MediLink/MediLink_ERA_decoder.py +4 -4
- MediLink/MediLink_Gmail.py +479 -4
- MediLink/MediLink_Mailer.py +0 -0
- MediLink/MediLink_Parser.py +111 -0
- MediLink/MediLink_Scan.py +0 -0
- MediLink/MediLink_Scheduler.py +2 -131
- MediLink/MediLink_StatusCheck.py +0 -4
- MediLink/MediLink_UI.py +87 -27
- MediLink/MediLink_Up.py +301 -45
- MediLink/MediLink_batch.bat +1 -1
- MediLink/test.py +74 -0
- medicafe-0.240613.0.dist-info/METADATA +55 -0
- medicafe-0.240613.0.dist-info/RECORD +43 -0
- {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/WHEEL +5 -5
- medicafe-0.240419.2.dist-info/METADATA +0 -19
- medicafe-0.240419.2.dist-info/RECORD +0 -32
- {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/LICENSE +0 -0
- {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
import re #for addresses
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
# Add parent directory of the project to the Python path
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
9
|
+
sys.path.append(project_dir)
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from MediLink import MediLink_ConfigLoader
|
|
13
|
+
config, crosswalk = MediLink_ConfigLoader.load_configuration()
|
|
14
|
+
except ImportError:
|
|
15
|
+
from MediLink_ConfigLoader import load_configuration
|
|
16
|
+
config, crosswalk = load_configuration()
|
|
17
|
+
|
|
18
|
+
from MediBot_Preprocessor_lib import open_csv_for_editing, initialize
|
|
19
|
+
from MediBot_UI import manage_script_pause, app_control
|
|
20
|
+
|
|
6
21
|
|
|
7
22
|
# Bring in all the constants
|
|
8
23
|
initialize(config)
|
|
@@ -11,11 +26,13 @@ initialize(config)
|
|
|
11
26
|
def format_name(value):
|
|
12
27
|
if ',' in value:
|
|
13
28
|
return value
|
|
14
|
-
hyphenated_name_pattern = r'(?P<First>[\w-]+)\s+(?P<Middle>[\w-]
|
|
29
|
+
hyphenated_name_pattern = r'(?P<First>[\w-]+)\s+(?P<Middle>[\w-]?)\s+(?P<Last>[\w-]+)'
|
|
15
30
|
match = re.match(hyphenated_name_pattern, value)
|
|
16
31
|
if match:
|
|
17
32
|
first_name = match.group('First')
|
|
18
33
|
middle_name = match.group('Middle') or ''
|
|
34
|
+
if len(middle_name) > 1:
|
|
35
|
+
middle_name = middle_name[0] # take only the first character
|
|
19
36
|
last_name = match.group('Last')
|
|
20
37
|
return '{}, {} {}'.format(last_name, first_name, middle_name).strip()
|
|
21
38
|
parts = value.split()
|
|
@@ -43,70 +60,97 @@ def format_policy(value):
|
|
|
43
60
|
def format_gender(value):
|
|
44
61
|
return value[0].upper()
|
|
45
62
|
|
|
63
|
+
def enforce_significant_length(output):
|
|
64
|
+
# Replace spaces with a placeholder that counts as one significant digit
|
|
65
|
+
temp_output = output.replace('{Space}', ' ')
|
|
66
|
+
|
|
67
|
+
# Check if the number of significant digits exceeds 30
|
|
68
|
+
if len(temp_output) > 30:
|
|
69
|
+
|
|
70
|
+
# First line of defense: Replace ' APT ' with ' #' if the original length is longer than 30 characters.
|
|
71
|
+
temp_output = temp_output.replace(' APT ', ' #')
|
|
72
|
+
|
|
73
|
+
# Remove spaces in a controlled manner from right to left if still too long
|
|
74
|
+
while len(temp_output) > 30:
|
|
75
|
+
# Find the last space
|
|
76
|
+
last_space_index = temp_output.rfind(' ')
|
|
77
|
+
if last_space_index == -1:
|
|
78
|
+
break
|
|
79
|
+
# Remove the last space
|
|
80
|
+
temp_output = temp_output[:last_space_index] + temp_output[last_space_index+7:]
|
|
81
|
+
|
|
82
|
+
# If still greater than 30, truncate to 30 characters
|
|
83
|
+
if len(temp_output) > 30:
|
|
84
|
+
temp_output = temp_output[:30]
|
|
85
|
+
|
|
86
|
+
# Replace placeholder back with actual space for final return
|
|
87
|
+
return temp_output.replace(' ', '{Space}')
|
|
88
|
+
|
|
46
89
|
def format_street(value, csv_data, reverse_mapping, parsed_address_components):
|
|
47
|
-
|
|
48
|
-
|
|
90
|
+
# Temporarily disable script pause status
|
|
91
|
+
app_control.set_pause_status(False)
|
|
49
92
|
|
|
50
|
-
# Remove
|
|
93
|
+
# Remove period characters.
|
|
51
94
|
value = value.replace('.', '')
|
|
52
95
|
|
|
53
|
-
#
|
|
96
|
+
# Proceed only if there's a comma, indicating a likely full address
|
|
54
97
|
if ',' in value:
|
|
55
98
|
try:
|
|
56
|
-
|
|
99
|
+
MediLink_ConfigLoader.log("Attempting to resolve address via regex...")
|
|
100
|
+
# Retrieve common city names from configuration and prepare a regex pattern
|
|
57
101
|
common_cities = config.get('cities', [])
|
|
58
|
-
|
|
59
|
-
# Convert cities to a case-insensitive regex pattern
|
|
60
102
|
city_pattern = '|'.join(re.escape(city) for city in common_cities)
|
|
61
103
|
city_regex_pattern = r'(?P<City>{})'.format(city_pattern)
|
|
62
104
|
city_regex = re.compile(city_regex_pattern, re.IGNORECASE)
|
|
63
105
|
|
|
64
|
-
#
|
|
106
|
+
# Search for a common city in the address
|
|
65
107
|
city_match = city_regex.search(value)
|
|
66
108
|
|
|
67
109
|
if city_match:
|
|
68
|
-
|
|
110
|
+
# Extract city name and partition the value around it
|
|
111
|
+
city = city_match.group('City').upper()
|
|
69
112
|
street, _, remainder = value.partition(city)
|
|
70
|
-
|
|
71
|
-
#
|
|
113
|
+
|
|
114
|
+
# Regex pattern to find state and zip code in the remainder
|
|
72
115
|
address_pattern = r',\s*(?P<State>[A-Z]{2})\s*(?P<Zip>\d{5}(?:-\d{4})?)?'
|
|
73
|
-
|
|
74
116
|
match = re.search(address_pattern, remainder)
|
|
75
|
-
|
|
117
|
+
|
|
76
118
|
if match:
|
|
77
|
-
# Update
|
|
119
|
+
# Update parsed address components
|
|
78
120
|
parsed_address_components['City'] = city
|
|
79
121
|
parsed_address_components['State'] = match.group('State')
|
|
80
122
|
parsed_address_components['Zip Code'] = match.group('Zip')
|
|
81
|
-
|
|
82
|
-
return street.strip()
|
|
123
|
+
# Return formatted street address, enforcing significant length
|
|
124
|
+
return enforce_significant_length(street.strip())
|
|
83
125
|
else:
|
|
84
|
-
# Fallback
|
|
85
|
-
# value = street + ', ' + city + remainder
|
|
126
|
+
# Fallback regex for parsing addresses without a common city
|
|
86
127
|
address_pattern = r'(?P<Street>[\w\s]+),?\s+(?P<City>[\w\s]+),\s*(?P<State>[A-Z]{2})\s*(?P<Zip>\d{5}(-\d{4})?)'
|
|
87
128
|
match = re.match(address_pattern, value)
|
|
88
129
|
|
|
89
130
|
if match:
|
|
90
|
-
# Update
|
|
131
|
+
# Update parsed address components
|
|
91
132
|
parsed_address_components['City'] = match.group('City')
|
|
92
133
|
parsed_address_components['State'] = match.group('State')
|
|
93
134
|
parsed_address_components['Zip Code'] = match.group('Zip')
|
|
94
|
-
|
|
95
|
-
return match.group('Street').strip()
|
|
135
|
+
# Return formatted street address, enforcing significant length
|
|
136
|
+
return enforce_significant_length(match.group('Street').strip())
|
|
96
137
|
|
|
97
138
|
except Exception as e:
|
|
139
|
+
# Handle exceptions by logging and offering to correct data manually
|
|
98
140
|
print("Address format error: Unable to parse address '{}'. Error: {}".format(value, e))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return value.replace(' ', '{Space}')
|
|
141
|
+
app_control.set_pause_status(True)
|
|
142
|
+
open_csv_for_editing(CSV_FILE_PATH)
|
|
143
|
+
manage_script_pause(csv_data, e, reverse_mapping)
|
|
144
|
+
# Return original value with spaces formatted, enforcing significant length
|
|
145
|
+
return enforce_significant_length(value.replace(' ', '{Space}'))
|
|
104
146
|
else:
|
|
105
|
-
# If no comma is present,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
147
|
+
# If no comma is present, treat the input as a simple street name
|
|
148
|
+
formatted_value = value.replace(' ', '{Space}')
|
|
149
|
+
enforced_format = enforce_significant_length(formatted_value)
|
|
150
|
+
return enforced_format
|
|
151
|
+
|
|
152
|
+
# Fallback return in case no address components are matched even though a comma was present
|
|
153
|
+
return enforce_significant_length(value.replace(' ', '{Space}'))
|
|
110
154
|
|
|
111
155
|
def format_zip(value):
|
|
112
156
|
# Ensure the value is a string, in case it's provided as an integer
|
|
@@ -138,7 +182,7 @@ def format_data(medisoft_field, value, csv_data, reverse_mapping, parsed_address
|
|
|
138
182
|
elif medisoft_field == 'Secondary Group Number':
|
|
139
183
|
formatted_value = format_policy(value)
|
|
140
184
|
else:
|
|
141
|
-
formatted_value = value
|
|
185
|
+
formatted_value = str(value) # Ensure value is always a string
|
|
142
186
|
|
|
143
187
|
formatted_value = formatted_value.replace(',', '{,}').replace(' ', '{Space}')
|
|
144
188
|
ahk_command = 'SendInput, {}{{Enter}}'.format(formatted_value)
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
from docx import Document
|
|
2
|
+
import re
|
|
3
|
+
from lxml import etree
|
|
4
|
+
import zipfile
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from collections import OrderedDict
|
|
9
|
+
|
|
10
|
+
# Add parent directory of the project to the Python path
|
|
11
|
+
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
12
|
+
sys.path.append(project_dir)
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import MediLink_ConfigLoader
|
|
16
|
+
except ImportError:
|
|
17
|
+
from MediLink import MediLink_ConfigLoader
|
|
18
|
+
|
|
19
|
+
def parse_docx(filepath):
|
|
20
|
+
try:
|
|
21
|
+
doc = Document(filepath) # Open the .docx file
|
|
22
|
+
except Exception as e:
|
|
23
|
+
MediLink_ConfigLoader.log("Error opening document: {}".format(e)) # Log error
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
patient_data = OrderedDict() # Initialize OrderedDict to store data
|
|
27
|
+
MediLink_ConfigLoader.log("Extracting Date of Service from {}".format(filepath), level="DEBUG")
|
|
28
|
+
|
|
29
|
+
date_of_service = extract_date_of_service(filepath) # Extract date of service
|
|
30
|
+
MediLink_ConfigLoader.log("Date of Service recorded as: {}".format(date_of_service), level="DEBUG")
|
|
31
|
+
|
|
32
|
+
for table in doc.tables: # Iterate over tables in the document
|
|
33
|
+
for row in table.rows:
|
|
34
|
+
cells = [cell.text.strip() for cell in row.cells]
|
|
35
|
+
if len(cells) > 4 and cells[3].startswith('#'):
|
|
36
|
+
try:
|
|
37
|
+
patient_id = parse_patient_id(cells[3])
|
|
38
|
+
diagnosis_code = parse_diagnosis_code(cells[4])
|
|
39
|
+
left_or_right_eye = parse_left_or_right_eye(cells[4])
|
|
40
|
+
femto_yes_or_no = parse_femto_yes_or_no(cells[4])
|
|
41
|
+
|
|
42
|
+
if patient_id not in patient_data:
|
|
43
|
+
patient_data[patient_id] = {}
|
|
44
|
+
|
|
45
|
+
if date_of_service in patient_data[patient_id]:
|
|
46
|
+
MediLink_ConfigLoader.log("Duplicate entry for patient ID {} on date {}. Skipping.".format(patient_id, date_of_service))
|
|
47
|
+
else:
|
|
48
|
+
patient_data[patient_id][date_of_service] = [diagnosis_code, left_or_right_eye, femto_yes_or_no]
|
|
49
|
+
except Exception as e:
|
|
50
|
+
MediLink_ConfigLoader.log("Error processing row: {}. Error: {}".format(cells, e))
|
|
51
|
+
|
|
52
|
+
# Validation steps
|
|
53
|
+
validate_unknown_entries(patient_data)
|
|
54
|
+
validate_diagnostic_code(patient_data)
|
|
55
|
+
|
|
56
|
+
return patient_data
|
|
57
|
+
|
|
58
|
+
def validate_unknown_entries(patient_data):
|
|
59
|
+
for patient_id, dates in list(patient_data.items()):
|
|
60
|
+
for date, details in list(dates.items()):
|
|
61
|
+
if 'Unknown' in details:
|
|
62
|
+
warning_message = "Warning: 'Unknown' entry found. Patient ID: {}, Date: {}, Details: {}".format(patient_id, date, details)
|
|
63
|
+
MediLink_ConfigLoader.log(warning_message, level="WARNING")
|
|
64
|
+
print(warning_message)
|
|
65
|
+
del patient_data[patient_id][date]
|
|
66
|
+
if not patient_data[patient_id]: # If no dates left for the patient, remove the patient
|
|
67
|
+
del patient_data[patient_id]
|
|
68
|
+
|
|
69
|
+
def validate_diagnostic_code(patient_data):
|
|
70
|
+
for patient_id, dates in patient_data.items():
|
|
71
|
+
for date, details in dates.items():
|
|
72
|
+
diagnostic_code, eye, _ = details
|
|
73
|
+
if diagnostic_code[-1].isdigit():
|
|
74
|
+
if eye == 'Left' and not diagnostic_code.endswith('2'):
|
|
75
|
+
log_and_warn(patient_id, date, diagnostic_code, eye)
|
|
76
|
+
elif eye == 'Right' and not diagnostic_code.endswith('1'):
|
|
77
|
+
log_and_warn(patient_id, date, diagnostic_code, eye)
|
|
78
|
+
|
|
79
|
+
def log_and_warn(patient_id, date, diagnostic_code, eye):
|
|
80
|
+
warning_message = (
|
|
81
|
+
"Warning: Mismatch found for Patient ID: {}, Date: {}, "
|
|
82
|
+
"Diagnostic Code: {}, Eye: {}".format(patient_id, date, diagnostic_code, eye)
|
|
83
|
+
)
|
|
84
|
+
MediLink_ConfigLoader.log(warning_message, level="WARNING")
|
|
85
|
+
print(warning_message)
|
|
86
|
+
|
|
87
|
+
# Extract and parse the date of service from the .docx file
|
|
88
|
+
def extract_date_of_service(docx_path):
|
|
89
|
+
extract_to = "extracted_docx"
|
|
90
|
+
try:
|
|
91
|
+
if not os.path.exists(extract_to):
|
|
92
|
+
os.makedirs(extract_to)
|
|
93
|
+
with zipfile.ZipFile(docx_path, 'r') as docx:
|
|
94
|
+
docx.extractall(extract_to)
|
|
95
|
+
MediLink_ConfigLoader.log("Extracted DOCX to: {}".format(extract_to), level="DEBUG")
|
|
96
|
+
|
|
97
|
+
file_path = find_text_in_xml(extract_to, "Surgery Schedule")
|
|
98
|
+
if file_path:
|
|
99
|
+
return extract_date_from_file(file_path)
|
|
100
|
+
else:
|
|
101
|
+
MediLink_ConfigLoader.log("Target text 'Surgery Schedule' not found in any XML files.", level="WARNING")
|
|
102
|
+
return None
|
|
103
|
+
finally:
|
|
104
|
+
# Clean up the extracted files
|
|
105
|
+
remove_directory(extract_to)
|
|
106
|
+
MediLink_ConfigLoader.log("Cleaned up extracted files in: {}".format(extract_to), level="DEBUG")
|
|
107
|
+
|
|
108
|
+
def remove_directory(path):
|
|
109
|
+
if os.path.exists(path):
|
|
110
|
+
for root, dirs, files in os.walk(path, topdown=False):
|
|
111
|
+
for name in files:
|
|
112
|
+
os.remove(os.path.join(root, name))
|
|
113
|
+
for name in dirs:
|
|
114
|
+
os.rmdir(os.path.join(root, name))
|
|
115
|
+
os.rmdir(path)
|
|
116
|
+
|
|
117
|
+
# Find the target text in the extracted XML files
|
|
118
|
+
def find_text_in_xml(directory, target_text):
|
|
119
|
+
for root_dir, dirs, files in os.walk(directory):
|
|
120
|
+
for file in files:
|
|
121
|
+
if file.endswith('.xml'):
|
|
122
|
+
file_path = os.path.join(root_dir, file)
|
|
123
|
+
try:
|
|
124
|
+
tree = etree.parse(file_path)
|
|
125
|
+
root = tree.getroot()
|
|
126
|
+
namespaces = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'} # hardcoded for XP handling BUG
|
|
127
|
+
for elem in root.xpath('//w:t', namespaces=namespaces):
|
|
128
|
+
if elem.text and target_text in elem.text:
|
|
129
|
+
MediLink_ConfigLoader.log("Found target text in file: {}".format(file_path), level="DEBUG")
|
|
130
|
+
return file_path
|
|
131
|
+
except Exception as e:
|
|
132
|
+
MediLink_ConfigLoader.log("Error parsing XML file {}: {}".format(file_path, e))
|
|
133
|
+
print("Error parsing XML file {}: {}".format(file_path, e))
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
# Normalize month and day abbreviations
|
|
137
|
+
def normalize_text(text):
|
|
138
|
+
month_map = {
|
|
139
|
+
'JAN': 'JANUARY', 'FEB': 'FEBRUARY', 'MAR': 'MARCH', 'APR': 'APRIL',
|
|
140
|
+
'MAY': 'MAY', 'JUN': 'JUNE', 'JUL': 'JULY', 'AUG': 'AUGUST',
|
|
141
|
+
'SEP': 'SEPTEMBER', 'OCT': 'OCTOBER', 'NOV': 'NOVEMBER', 'DEC': 'DECEMBER'
|
|
142
|
+
}
|
|
143
|
+
day_map = {
|
|
144
|
+
'MON': 'MONDAY', 'TUE': 'TUESDAY', 'WED': 'WEDNESDAY', 'THU': 'THURSDAY',
|
|
145
|
+
'FRI': 'FRIDAY', 'SAT': 'SATURDAY', 'SUN': 'SUNDAY'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for abbr, full in month_map.items():
|
|
149
|
+
text = re.sub(r'\b' + abbr + r'\b', full, text, flags=re.IGNORECASE)
|
|
150
|
+
for abbr, full in day_map.items():
|
|
151
|
+
text = re.sub(r'\b' + abbr + r'\b', full, text, flags=re.IGNORECASE)
|
|
152
|
+
|
|
153
|
+
return text
|
|
154
|
+
|
|
155
|
+
def reassemble_year(text):
|
|
156
|
+
# First, handle the most common case where a 4-digit year is split as (3,1), (1,3), or (2,2)
|
|
157
|
+
text = re.sub(r'(\d{3}) (\d{1})', r'\1\2', text)
|
|
158
|
+
text = re.sub(r'(\d{1}) (\d{3})', r'\1\2', text)
|
|
159
|
+
text = re.sub(r'(\d{2}) (\d{2})', r'\1\2', text)
|
|
160
|
+
|
|
161
|
+
# Handle the less common cases where the year might be split as (1,1,2) or (2,1,1) or (1,2,1)
|
|
162
|
+
parts = re.findall(r'\b(\d{1,2})\b', text)
|
|
163
|
+
if len(parts) >= 4:
|
|
164
|
+
for i in range(len(parts) - 3):
|
|
165
|
+
candidate = ''.join(parts[i:i + 4])
|
|
166
|
+
if len(candidate) == 4 and candidate.isdigit():
|
|
167
|
+
combined_year = candidate
|
|
168
|
+
text = re.sub(r'\b' + r'\b \b'.join(parts[i:i + 4]) + r'\b', combined_year, text)
|
|
169
|
+
break
|
|
170
|
+
|
|
171
|
+
return text
|
|
172
|
+
|
|
173
|
+
# Extract and parse the date from the file
|
|
174
|
+
def extract_date_from_file(file_path):
|
|
175
|
+
try:
|
|
176
|
+
tree = etree.parse(file_path)
|
|
177
|
+
root = tree.getroot()
|
|
178
|
+
collected_text = []
|
|
179
|
+
|
|
180
|
+
namespaces = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'} # hardcoded for XP handling BUG
|
|
181
|
+
for elem in root.xpath('//w:t', namespaces=namespaces):
|
|
182
|
+
if elem.text:
|
|
183
|
+
collected_text.append(elem.text.strip())
|
|
184
|
+
|
|
185
|
+
for elem in root.iter():
|
|
186
|
+
if elem.tag.endswith('t') and elem.text:
|
|
187
|
+
collected_text.append(elem.text.strip())
|
|
188
|
+
|
|
189
|
+
combined_text = ' '.join(collected_text)
|
|
190
|
+
combined_text = reassemble_year(combined_text) # Fix OCR splitting years
|
|
191
|
+
# combined_text = re.sub(r'(\d{3}) (\d{1})', r'\1\2', combined_text) # initial year regex.
|
|
192
|
+
combined_text = normalize_text(combined_text) # Normalize abbreviations
|
|
193
|
+
combined_text = re.sub(r',', '', combined_text) # Remove commas if they exist
|
|
194
|
+
|
|
195
|
+
# Log the combined text
|
|
196
|
+
MediLink_ConfigLoader.log("Combined text: {}".format(combined_text), level="DEBUG")
|
|
197
|
+
# print("DEBUG: Combined text: {}".format(combined_text))
|
|
198
|
+
|
|
199
|
+
day_week_pattern = r"(MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|SUNDAY)"
|
|
200
|
+
month_day_pattern = r"(JANUARY|FEBRUARY|MARCH|APRIL|MAY|JUNE|JULY|AUGUST|SEPTEMBER|OCTOBER|NOVEMBER|DECEMBER) \d{1,2}"
|
|
201
|
+
year_pattern = r"\d{4}"
|
|
202
|
+
|
|
203
|
+
day_of_week = re.search(day_week_pattern, combined_text, re.IGNORECASE)
|
|
204
|
+
month_day = re.search(month_day_pattern, combined_text, re.IGNORECASE)
|
|
205
|
+
year_match = re.search(year_pattern, combined_text, re.IGNORECASE)
|
|
206
|
+
|
|
207
|
+
# Log the results of the regex searches
|
|
208
|
+
MediLink_ConfigLoader.log("Day of week found: {}".format(day_of_week.group() if day_of_week else 'None'), level="DEBUG")
|
|
209
|
+
MediLink_ConfigLoader.log("Month and day found: {}".format(month_day.group() if month_day else 'None'), level="DEBUG")
|
|
210
|
+
MediLink_ConfigLoader.log("Year found: {}".format(year_match.group() if year_match else 'None'), level="DEBUG")
|
|
211
|
+
|
|
212
|
+
if day_of_week and month_day and year_match:
|
|
213
|
+
date_str = "{} {} {}".format(day_of_week.group(), month_day.group(), year_match.group())
|
|
214
|
+
try:
|
|
215
|
+
date_obj = datetime.strptime(date_str, '%A %B %d %Y')
|
|
216
|
+
return date_obj.strftime('%m-%d-%Y')
|
|
217
|
+
except ValueError as e:
|
|
218
|
+
MediLink_ConfigLoader.log("Error converting date: {}. Error: {}".format(date_str, e), level="ERROR")
|
|
219
|
+
else:
|
|
220
|
+
MediLink_ConfigLoader.log("Date components not found or incomplete in the text. Combined text: {}, Day of week: {}, Month and day: {}, Year: {}"
|
|
221
|
+
.format(combined_text,
|
|
222
|
+
day_of_week.group() if day_of_week else 'None',
|
|
223
|
+
month_day.group() if month_day else 'None',
|
|
224
|
+
year_match.group() if year_match else 'None'),
|
|
225
|
+
level="WARNING")
|
|
226
|
+
except Exception as e:
|
|
227
|
+
MediLink_ConfigLoader.log("Error extracting date from file: {}. Error: {}".format(file_path, e))
|
|
228
|
+
print("Error extracting date from file: {}. Error: {}".format(file_path, e))
|
|
229
|
+
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
def parse_patient_id(text):
|
|
233
|
+
try:
|
|
234
|
+
return text.split()[0].lstrip('#') # Extract patient ID number (removing the '#')
|
|
235
|
+
except Exception as e:
|
|
236
|
+
MediLink_ConfigLoader.log("Error parsing patient ID: {}. Error: {}".format(text, e))
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
def parse_diagnosis_code(text):
|
|
240
|
+
try:
|
|
241
|
+
if '(' in text and ')' in text: # Extract the diagnosis code before the '/'
|
|
242
|
+
full_code = text[text.index('(')+1:text.index(')')]
|
|
243
|
+
return full_code.split('/')[0]
|
|
244
|
+
return text.split('/')[0]
|
|
245
|
+
except Exception as e:
|
|
246
|
+
MediLink_ConfigLoader.log("Error parsing diagnosis code: {}. Error: {}".format(text, e))
|
|
247
|
+
return "Unknown"
|
|
248
|
+
|
|
249
|
+
def parse_left_or_right_eye(text):
|
|
250
|
+
try:
|
|
251
|
+
if 'LEFT EYE' in text.upper():
|
|
252
|
+
return 'Left'
|
|
253
|
+
elif 'RIGHT EYE' in text.upper():
|
|
254
|
+
return 'Right'
|
|
255
|
+
else:
|
|
256
|
+
return 'Unknown'
|
|
257
|
+
except Exception as e:
|
|
258
|
+
MediLink_ConfigLoader.log("Error parsing left or right eye: {}. Error: {}".format(text, e))
|
|
259
|
+
return 'Unknown'
|
|
260
|
+
|
|
261
|
+
def parse_femto_yes_or_no(text):
|
|
262
|
+
try:
|
|
263
|
+
if 'FEMTO' in text.upper():
|
|
264
|
+
return True
|
|
265
|
+
else:
|
|
266
|
+
return False
|
|
267
|
+
except Exception as e:
|
|
268
|
+
MediLink_ConfigLoader.log("Error parsing femto yes or no: {}. Error: {}".format(text, e))
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def rotate_docx_files(directory):
|
|
273
|
+
# List all files in the directory
|
|
274
|
+
files = os.listdir(directory)
|
|
275
|
+
|
|
276
|
+
# Filter files that contain "DR" and "SS" in the filename
|
|
277
|
+
filtered_files = [file for file in files if "DR" in file and "SS" in file]
|
|
278
|
+
|
|
279
|
+
# Iterate through filtered files
|
|
280
|
+
for filename in filtered_files:
|
|
281
|
+
filepath = os.path.join(directory, filename)
|
|
282
|
+
# Parse each document and print the resulting dictionary
|
|
283
|
+
patient_data_dict = parse_docx(filepath)
|
|
284
|
+
print("Data from file '{}':".format(filename))
|
|
285
|
+
import pprint
|
|
286
|
+
pprint.pprint(patient_data_dict)
|
|
287
|
+
print()
|
|
288
|
+
|
|
289
|
+
def main():
|
|
290
|
+
# Call the function with the directory containing your .docx files
|
|
291
|
+
directory = "C:\\Users\\danie\\Downloads\\"
|
|
292
|
+
rotate_docx_files(directory)
|
|
293
|
+
|
|
294
|
+
if __name__ == "__main__":
|
|
295
|
+
main()
|
MediBot/update_medicafe.py
CHANGED
|
@@ -1,17 +1,55 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
import sys
|
|
3
3
|
from tqdm import tqdm
|
|
4
|
+
import requests
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
def check_internet_connection():
|
|
8
|
+
try:
|
|
9
|
+
requests.get("http://www.google.com", timeout=5)
|
|
10
|
+
return True
|
|
11
|
+
except requests.ConnectionError:
|
|
12
|
+
return False
|
|
4
13
|
|
|
5
14
|
def upgrade_medicafe(package):
|
|
6
15
|
try:
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
# Check internet connection
|
|
17
|
+
if not check_internet_connection():
|
|
18
|
+
print("Error: No internet connection detected. Please check your internet connection and try again.")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
total_progress = 200 # Total progress for two runs
|
|
22
|
+
|
|
23
|
+
with tqdm(total=total_progress, desc="Upgrading %s" % package, unit="%") as progress_bar:
|
|
24
|
+
stdout_accumulator = b""
|
|
25
|
+
stderr_accumulator = b""
|
|
26
|
+
|
|
27
|
+
for _ in range(2): # Run pip install twice
|
|
28
|
+
process = subprocess.Popen([sys.executable, '-m', 'pip', 'install', '--upgrade', package, '--no-cache-dir', '--disable-pip-version-check', '--no-deps'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
29
|
+
stdout, stderr = process.communicate()
|
|
30
|
+
stdout_accumulator += stdout
|
|
31
|
+
stderr_accumulator += stderr
|
|
32
|
+
|
|
33
|
+
if process.returncode != 0:
|
|
34
|
+
# If the return code is non-zero, print error details
|
|
35
|
+
print("Error: Upgrade failed. Details:")
|
|
36
|
+
print("stdout:", stdout)
|
|
37
|
+
print("stderr:", stderr)
|
|
38
|
+
sys.exit(1)
|
|
39
|
+
|
|
40
|
+
progress_bar.update(total_progress // 2) # Update progress bar
|
|
41
|
+
|
|
42
|
+
# Add a 3-second sleep between runs
|
|
43
|
+
time.sleep(3)
|
|
44
|
+
|
|
45
|
+
progress_bar.update(total_progress // 2) # Update progress bar
|
|
46
|
+
print("stdout:", stdout_accumulator.decode("utf-8"))
|
|
47
|
+
print("stderr:", stderr_accumulator.decode("utf-8"))
|
|
48
|
+
time.sleep(1)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
# Log any other exceptions
|
|
51
|
+
print("Error:", e)
|
|
52
|
+
time.sleep(3)
|
|
15
53
|
sys.exit(1)
|
|
16
54
|
|
|
17
55
|
if __name__ == "__main__":
|