medicafe 0.240517.0__py3-none-any.whl → 0.240716.2__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.

Files changed (37) hide show
  1. MediBot/MediBot.bat +46 -6
  2. MediBot/MediBot.py +9 -36
  3. MediBot/MediBot_Charges.py +0 -28
  4. MediBot/MediBot_Crosswalk_Library.py +16 -8
  5. MediBot/MediBot_Post.py +0 -0
  6. MediBot/MediBot_Preprocessor.py +26 -63
  7. MediBot/MediBot_Preprocessor_lib.py +182 -43
  8. MediBot/MediBot_UI.py +2 -7
  9. MediBot/MediBot_dataformat_library.py +0 -9
  10. MediBot/MediBot_docx_decoder.py +283 -60
  11. MediLink/MediLink.py +80 -120
  12. MediLink/MediLink_837p_encoder.py +3 -28
  13. MediLink/MediLink_837p_encoder_library.py +19 -53
  14. MediLink/MediLink_API_Generator.py +246 -0
  15. MediLink/MediLink_API_v2.py +2 -0
  16. MediLink/MediLink_API_v3.py +325 -0
  17. MediLink/MediLink_APIs.py +2 -0
  18. MediLink/MediLink_ClaimStatus.py +144 -0
  19. MediLink/MediLink_ConfigLoader.py +13 -7
  20. MediLink/MediLink_DataMgmt.py +224 -68
  21. MediLink/MediLink_Decoder.py +165 -0
  22. MediLink/MediLink_Deductible.py +203 -0
  23. MediLink/MediLink_Down.py +122 -96
  24. MediLink/MediLink_Gmail.py +453 -74
  25. MediLink/MediLink_Mailer.py +0 -7
  26. MediLink/MediLink_Parser.py +193 -0
  27. MediLink/MediLink_Scan.py +0 -0
  28. MediLink/MediLink_Scheduler.py +2 -172
  29. MediLink/MediLink_StatusCheck.py +0 -4
  30. MediLink/MediLink_UI.py +54 -18
  31. MediLink/MediLink_Up.py +6 -15
  32. {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/METADATA +4 -1
  33. medicafe-0.240716.2.dist-info/RECORD +47 -0
  34. {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/WHEEL +1 -1
  35. medicafe-0.240517.0.dist-info/RECORD +0 -39
  36. {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/LICENSE +0 -0
  37. {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/top_level.txt +0 -0
@@ -17,37 +17,13 @@ except ImportError:
17
17
 
18
18
  try:
19
19
  from MediBot_UI import app_control
20
+ from MediBot_docx_decoder import parse_docx
20
21
  except ImportError:
21
22
  from MediBot import MediBot_UI
22
23
  app_control = MediBot_UI.app_control
24
+ from MediBot import MediBot_docx_decoder
25
+ parse_docx = MediBot_docx_decoder.parse_docx
23
26
 
24
- """
25
- Draft Docstring to move over from Preprocessor.
26
-
27
- Data Integrity and Validation
28
- Implement a mechanism to confirm the accuracy of entered data, potentially through a verification step or summary report.
29
- Enhance CSV integrity checks to identify and report potential issues with data format, especially concerning insurance policy numbers and special character handling.
30
-
31
- Development Roadmap for crosswalk_update()
32
- Automation required for updating the crosswalk.json when new Medisoft insurance is discovered.
33
- For open_csv_for_editing
34
-
35
- Known Issues and Bugs
36
- Address the handling of '.' and other special characters that may disrupt parsing, especially under Windows XP.
37
-
38
- For load_csv_data
39
-
40
- Preprocessing Enhancements
41
- Optimize script startup and CSV loading to reduce initial latency.
42
-
43
- Data Integrity and Validation
44
- Conduct a thorough CSV integrity check before processing to flag potential issues upfront.
45
-
46
- Future Work
47
- Consolidate data from multiple sources (Provider_Notes.csv, Surgery_Schedule.csv, and Carols_CSV.csv) into a single table with Patient ID as the key, ensuring all data elements are aligned and duplicate entries are minimized.
48
- Implement logic to verify and match Patient IDs across different files to ensure data integrity before consolidation. (Catching errors between source data)
49
- Optimize the preprocessing of surgery dates and diagnosis codes for use in patient billing and scheduling systems.
50
- """
51
27
  class InitializationError(Exception):
52
28
  def __init__(self, message):
53
29
  self.message = message
@@ -105,13 +81,32 @@ def load_csv_data(csv_file_path):
105
81
  sys.exit(1) # Halt the script in case of other IO errors
106
82
 
107
83
  # CSV Pre-processor Helper functions
108
- def add_insurance_id_column(csv_data):
84
+ def add_columns(csv_data, column_headers):
85
+ """
86
+ Adds one or multiple columns to the CSV data.
87
+
88
+ Parameters:
89
+ csv_data (list of dict): The CSV data where each row is represented as a dictionary.
90
+ column_headers (list of str or str): A list of column headers to be added to each row, or a single column header.
91
+
92
+ Returns:
93
+ None: The function modifies the csv_data in place.
94
+ """
95
+ if isinstance(column_headers, str):
96
+ column_headers = [column_headers]
97
+ elif not isinstance(column_headers, list):
98
+ raise ValueError("column_headers should be a list or a string")
99
+
109
100
  for row in csv_data:
110
- row['Ins1 Insurance ID'] = '' # Initialize the column with empty values
101
+ for header in column_headers:
102
+ row[header] = '' # Initialize the column with empty values
111
103
 
104
+ # Extracting the list to a variable for future refactoring:
112
105
  def filter_rows(csv_data):
106
+ # TODO This should go to the crosswalk.
107
+ excluded_insurance = ['AETNA', 'AETNA MEDICARE', 'HUMANA MED HMO']
113
108
  csv_data[:] = [row for row in csv_data if row.get('Patient ID', '').strip()]
114
- csv_data[:] = [row for row in csv_data if row.get('Primary Insurance', '').strip() not in ['AETNA', 'AETNA MEDICARE', 'HUMANA MED HMO']]
109
+ csv_data[:] = [row for row in csv_data if row.get('Primary Insurance', '').strip() not in excluded_insurance]
115
110
 
116
111
  def convert_surgery_date(csv_data):
117
112
  for row in csv_data:
@@ -121,6 +116,7 @@ def convert_surgery_date(csv_data):
121
116
  row['Surgery Date'] = datetime.min # Assign a minimum datetime value for sorting purposes
122
117
 
123
118
  def sort_and_deduplicate(csv_data):
119
+ # TODO we need to figure out a new logic here for doing second-eye charges. I don't know what the flow should be yet.
124
120
  csv_data.sort(key=lambda x: (x['Surgery Date'], x.get('Patient Last', '').strip()))
125
121
  unique_patients = {}
126
122
  for row in csv_data:
@@ -128,6 +124,8 @@ def sort_and_deduplicate(csv_data):
128
124
  if patient_id not in unique_patients or row['Surgery Date'] < unique_patients[patient_id]['Surgery Date']:
129
125
  unique_patients[patient_id] = row
130
126
  csv_data[:] = list(unique_patients.values())
127
+ # TODO Sorting, now that we're going to have the Surgery Schedules available, should be ordered as the patients show up on the schedule.
128
+ # If we don't have that surgery schedule yet for some reason, we should default to the current ordering strategy.
131
129
  csv_data.sort(key=lambda x: (x['Surgery Date'], x.get('Patient Last', '').strip()))
132
130
 
133
131
  def combine_fields(csv_data):
@@ -177,6 +175,149 @@ def update_insurance_ids(csv_data, crosswalk):
177
175
  'endpoint': 'OPTUMEDI' # Default probably should be a flag for the crosswalk update function to deal with. BUG HARDCODE THERE ARE 3 of these defaults
178
176
  }
179
177
 
178
+ def update_procedure_codes(csv_data):
179
+ # Define the Medisoft shorthand to diagnostic codes dictionary
180
+ # TODO The reverse of this will be in the crosswalk. We'll need to reverse it here for lookup.
181
+ medisoft_to_diagnosis = {
182
+ "25811": "H25.811",
183
+ "25812": "H25.812",
184
+ "2512": "H25.12",
185
+ "2511": "H25.11",
186
+ "529XA": "T85.29XA",
187
+ "4301": "H43.01",
188
+ "4302": "H43.02",
189
+ "011X2": "H40.11X2",
190
+ "051X3": "H40.51X3",
191
+ "5398A": "T85.398A"
192
+ }
193
+
194
+ # Define the procedure codes to diagnostic codes dictionary
195
+ procedure_to_diagnosis = {
196
+ "00142": ["H25.811", "H25.812", "H25.12", "H25.11", "T85.29XA"],
197
+ "00145": ["H43.01", "H43.02"],
198
+ "00140": ["H40.11X2", "H40.51X3"]
199
+ }
200
+
201
+ # Reverse the dictionary for easier lookup from diagnostic code to procedure code
202
+ diagnosis_to_procedure = {}
203
+ for procedure_code, diagnosis_codes in procedure_to_diagnosis.items():
204
+ for diagnosis_code in diagnosis_codes:
205
+ diagnosis_to_procedure[diagnosis_code] = procedure_code
206
+
207
+ # Initialize counter for updated rows
208
+ updated_count = 0
209
+
210
+ # Update the "Procedure Code" column in the CSV data
211
+ for row_num, row in enumerate(csv_data, start=1):
212
+ try:
213
+ medisoft_code = row.get('Default Diagnosis #1', '').strip()
214
+ diagnosis_code = medisoft_to_diagnosis.get(medisoft_code)
215
+ if diagnosis_code:
216
+ procedure_code = diagnosis_to_procedure.get(diagnosis_code)
217
+ if procedure_code:
218
+ row['Procedure Code'] = procedure_code
219
+ updated_count += 1
220
+ else:
221
+ row['Procedure Code'] = "Unknown" # Or handle as appropriate
222
+ else:
223
+ row['Procedure Code'] = "Unknown" # Or handle as appropriate
224
+ except Exception as e:
225
+ MediLink_ConfigLoader.log("In update_procedure_codes, Error processing row {}: {}".format(row_num, e), level="ERROR")
226
+
227
+ # Log total count of updated rows
228
+ MediLink_ConfigLoader.log("Total {} 'Procedure Code' rows updated.".format(updated_count), level="INFO")
229
+
230
+ return True
231
+
232
+ def update_diagnosis_codes(csv_data):
233
+ try:
234
+ # Load configuration and crosswalk
235
+ config, _ = MediLink_ConfigLoader.load_configuration()
236
+
237
+ # Extract the local storage path from the configuration
238
+ local_storage_path = config['MediLink_Config']['local_storage_path']
239
+
240
+ # Initialize a dictionary to hold diagnosis codes from all DOCX files
241
+ all_patient_data = {}
242
+
243
+ # Iterate through all files in the specified directory
244
+ for filename in os.listdir(local_storage_path):
245
+ if filename.endswith(".docx"):
246
+ filepath = os.path.join(local_storage_path, filename)
247
+ MediLink_ConfigLoader.log("Processing DOCX file: {}".format(filepath), level="INFO")
248
+ try:
249
+ patient_data = parse_docx(filepath)
250
+ for patient_id, service_dates in patient_data.items():
251
+ if patient_id not in all_patient_data:
252
+ all_patient_data[patient_id] = {}
253
+ for date_of_service, diagnosis_data in service_dates.items():
254
+ all_patient_data[patient_id][date_of_service] = diagnosis_data
255
+ except Exception as e:
256
+ MediLink_ConfigLoader.log("Error parsing DOCX file {}: {}".format(filepath, e), level="ERROR")
257
+
258
+ # Debug logging for all_patient_data
259
+ MediLink_ConfigLoader.log("All patient data collected from DOCX files: {}".format(all_patient_data), level="INFO")
260
+
261
+ # Define the diagnosis to Medisoft shorthand dictionary
262
+ diagnosis_to_medisoft = {
263
+ "H25.811": "25811",
264
+ "H25.812": "25812",
265
+ "H25.12": "2512",
266
+ "H25.11": "2511",
267
+ "T85.29XA": "529XA",
268
+ "H43.01": "4301",
269
+ "H43.02": "4302",
270
+ "H40.11X2": "011X2",
271
+ "H40.51X3": "051X3",
272
+ "T85.398A": "5398A"
273
+ }
274
+
275
+ # Convert surgery dates in CSV data
276
+ convert_surgery_date(csv_data)
277
+
278
+ # Initialize counter for updated rows
279
+ updated_count = 0
280
+
281
+ # Update the "Default Diagnosis #1" column in the CSV data
282
+ for row_num, row in enumerate(csv_data, start=1):
283
+ MediLink_ConfigLoader.log("Processing row number {}.".format(row_num), level="INFO")
284
+ patient_id = row.get('Patient ID', '').strip()
285
+ surgery_date = row.get('Surgery Date', '')
286
+
287
+ # Convert surgery_date to string format for lookup
288
+ if surgery_date != datetime.min:
289
+ surgery_date_str = surgery_date.strftime("%m-%d-%Y")
290
+ else:
291
+ surgery_date_str = ''
292
+
293
+ MediLink_ConfigLoader.log("Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="INFO")
294
+
295
+ if patient_id in all_patient_data:
296
+ if surgery_date_str in all_patient_data[patient_id]:
297
+ diagnosis_code, left_or_right_eye, femto_yes_or_no = all_patient_data[patient_id][surgery_date_str]
298
+ MediLink_ConfigLoader.log("Found diagnosis data for Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="INFO")
299
+
300
+ # Convert diagnosis code to Medisoft shorthand format.
301
+ defaulted_code = diagnosis_code[1:].replace('.', '')[-5:] if diagnosis_code else ''
302
+ medisoft_shorthand = diagnosis_to_medisoft.get(diagnosis_code, defaulted_code)
303
+ MediLink_ConfigLoader.log("Converted diagnosis code to Medisoft shorthand: {}".format(medisoft_shorthand), level="INFO")
304
+
305
+ row['Default Diagnosis #1'] = medisoft_shorthand
306
+ updated_count += 1
307
+ MediLink_ConfigLoader.log("Updated row number {} with new diagnosis code.".format(row_num), level="INFO")
308
+ else:
309
+ MediLink_ConfigLoader.log("No matching surgery date found for Patient ID: {} in row {}.".format(patient_id, row_num), level="INFO")
310
+ else:
311
+ MediLink_ConfigLoader.log("Patient ID: {} not found in DOCX data for row {}.".format(patient_id, row_num), level="INFO")
312
+
313
+ # Log total count of updated rows
314
+ MediLink_ConfigLoader.log("Total {} 'Default Diagnosis #1' rows updated.".format(updated_count), level="INFO")
315
+
316
+ except Exception as e:
317
+ message = "An error occurred while updating diagnosis codes. Please check the DOCX files and configuration: {}".format(e)
318
+ MediLink_ConfigLoader.log(message, level="ERROR")
319
+ print(message)
320
+
180
321
  def load_data_sources(config, crosswalk):
181
322
  """Loads historical mappings from MAPAT and Carol's CSVs."""
182
323
  patient_id_to_insurance_id = load_insurance_data_from_mapat(config, crosswalk)
@@ -269,36 +410,34 @@ def load_insurance_data_from_mapat(config, crosswalk):
269
410
  def parse_z_dat(z_dat_path, config):
270
411
  """
271
412
  Parses the Z.dat file to map Patient IDs to Insurance Names using the provided fixed-width file format.
272
-
413
+
273
414
  Args:
274
415
  z_dat_path (str): Path to the Z.dat file.
275
416
  config (dict): Configuration object containing slicing information and other parameters.
276
-
417
+
277
418
  Returns:
278
419
  dict: A dictionary mapping Patient IDs to Insurance Names.
279
420
  """
280
421
  patient_id_to_insurance_name = {}
281
-
422
+
282
423
  try:
283
- # Reading blocks of fixed-width data (3 lines per record: personal, insurance, service)
284
- for personal_info, insurance_info, service_info in MediLink_DataMgmt.read_fixed_width_data(z_dat_path):
424
+ # Reading blocks of fixed-width data (up to 5 lines per record)
425
+ for personal_info, insurance_info, service_info, service_info_2, service_info_3 in MediLink_DataMgmt.read_fixed_width_data(z_dat_path):
285
426
  # Parsing the data using slice definitions from the config
286
- parsed_data = MediLink_DataMgmt.parse_fixed_width_data(personal_info, insurance_info, service_info, config.get('MediLink_Config', config))
287
-
427
+ parsed_data = MediLink_DataMgmt.parse_fixed_width_data(personal_info, insurance_info, service_info, service_info_2, service_info_3, config.get('MediLink_Config', config))
428
+
288
429
  # Extract Patient ID and Insurance Name from parsed data
289
430
  patient_id = parsed_data.get('PATID')
290
431
  insurance_name = parsed_data.get('INAME')
291
-
432
+
292
433
  if patient_id and insurance_name:
293
434
  patient_id_to_insurance_name[patient_id] = insurance_name
294
435
  MediLink_ConfigLoader.log("Mapped Patient ID {} to Insurance Name {}".format(patient_id, insurance_name), config, level="INFO")
295
-
436
+
296
437
  except FileNotFoundError:
297
- MediLink_ConfigLoader.log("File not found: {}".format(z_dat_path), config, level="ERROR")
298
- raise
438
+ MediLink_ConfigLoader.log("File not found: {}".format(z_dat_path), config, level="INFO")
299
439
  except Exception as e:
300
- MediLink_ConfigLoader.log("Failed to parse Z.dat: {}".format(str(e)), config, level="ERROR")
301
- raise
440
+ MediLink_ConfigLoader.log("Failed to parse Z.dat: {}".format(str(e)), config, level="INFO")
302
441
 
303
442
  return patient_id_to_insurance_name
304
443
 
MediBot/MediBot_UI.py CHANGED
@@ -17,12 +17,7 @@ except ImportError:
17
17
  from MediLink_ConfigLoader import load_configuration
18
18
  config, crosswalk = load_configuration()
19
19
 
20
- """
21
- User Interaction Refinements
22
- - [ ] Refine the menu options for clearer user guidance during script pauses and errors.
23
- - [ ] Add functionality for user to easily repeat or skip specific entries without script restart.
24
- Develop more intuitive skip and retry mechanisms that are responsive to user input during data entry sessions.
25
- """
20
+
26
21
  # Function to check if a specific key is pressed
27
22
  VK_END = int(config.get('VK_END', ""), 16) # Try F12 (7B). Virtual key code for 'End' (23)
28
23
  VK_PAUSE = int(config.get('VK_PAUSE', ""), 16) # Try F11 (7A). Virtual-key code for 'Home' (24)
@@ -122,7 +117,7 @@ def display_patient_selection_menu(csv_data, reverse_mapping, proceed_as_medicar
122
117
  patient_name = row.get(patient_name_header, "Unknown")
123
118
  surgery_date = row.get('Surgery Date', "Unknown Date") # Access 'Surgery Date' as string directly from the row
124
119
 
125
- print("{0:03d}: {3:.5s} (ID: {2}) {1} ".format(index+1, patient_name, patient_id, surgery_date))
120
+ print("{0:03d}: {3:%m-%d} (ID: {2}) {1} ".format(index+1, patient_name, patient_id, surgery_date))
126
121
 
127
122
  displayed_indices.append(index)
128
123
  displayed_patient_ids.append(patient_id)
@@ -19,15 +19,6 @@ from MediBot_Preprocessor_lib import open_csv_for_editing, initialize
19
19
  from MediBot_UI import manage_script_pause, app_control
20
20
 
21
21
 
22
- """
23
- - [X] (TEST) Address Formatting 30-character Limit:
24
- (LOW) Address the issue where the format_street function in Medibot may produce addresses exceeding
25
- the 30-character limit. Current stop-gap is removing period characters and the abbreviation "APT"
26
- surrounded by spaces from all records as a temporary solution.
27
- If the address still exceeds 30 characters, the function will attempt to remove spaces from right to left
28
- until it reaches 30 significant digits or runs out of spaces, then truncate to 30 characters if necessary.
29
- """
30
-
31
22
  # Bring in all the constants
32
23
  initialize(config)
33
24