medicafe 0.240517.0__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 CHANGED
@@ -133,6 +133,7 @@ if "!internet_available!"=="0" (
133
133
  goto main_menu
134
134
  )
135
135
  echo Downloading emails...
136
+ :: move this path.
136
137
  py "../MediLink/MediLink_Gmail.py" "%firefox_path%"
137
138
  if errorlevel 1 (
138
139
  echo Failed to download emails.
@@ -161,6 +162,7 @@ if "!internet_available!"=="0" (
161
162
  )
162
163
  call :process_csvs
163
164
  cls
165
+ :: move this path.
164
166
  py "C:\Python34\Lib\site-packages\MediLink\MediLink.py"
165
167
  if errorlevel 1 echo MediLink failed to execute.
166
168
  pause
@@ -173,7 +175,13 @@ for /f "tokens=1-5 delims=/: " %%a in ('echo %time%') do (
173
175
  set "minute=%%b"
174
176
  set "second=%%c"
175
177
  )
176
- set "timestamp=%DATE:~-4%%DATE:~3,2%%DATE:~0,2%_%hour%%minute%"
178
+ for /f "tokens=2-4 delims=/ " %%a in ('echo %date%') do (
179
+ set "day=%%a"
180
+ set "month=%%b"
181
+ set "year=%%c"
182
+ )
183
+ set "timestamp=!year!!month!!day!_!hour!!minute!"
184
+
177
185
  set "latest_csv="
178
186
  for /f "delims=" %%a in ('dir /b /a-d /o-d "%source_folder%\*.csv" 2^>nul') do (
179
187
  set "latest_csv=%%a"
MediBot/MediBot.py CHANGED
@@ -22,41 +22,6 @@ except ImportError:
22
22
  from MediBot import MediBot_Crosswalk_Library
23
23
  crosswalk_update = MediBot_Crosswalk_Library.crosswalk_update
24
24
 
25
- """
26
- # Development Task List for MediBot
27
-
28
- Error Handling Improvements
29
- - [ ] Its really difficult to get out of the main menu if you go open MediBot by accident
30
- - [ ] Develop a centralized error handling and logging mechanism for improved troubleshooting.
31
- - [ ] Implement validation checks during patient data entry to prevent submission of incomplete or incorrect records.
32
-
33
- Insurance Mode Adjustments
34
- - [ ] Integrate a comprehensive list of insurance company codes for automatic selection.
35
- - [ ] Automate insurance-specific data entry adjustments, such as character replacements specific to Medicare.
36
-
37
- Diagnosis Entry
38
- - [ ] Automate data extraction from Surgery Schedule to import to CSV a column indicating diagnosis code per px.
39
-
40
- Script Efficiency and Reliability
41
- - [ ] Ensure robust handling of Medisoft's field navigation quirks, particularly for fields that are skipped or require special access.
42
-
43
- Documentation and Support
44
- - [ ] Create detailed documentation for setup, configuration, and usage of the script.
45
- - [ ] Establish a support channel for users to report issues or request features.
46
-
47
- Future Directions
48
- - [ ] Consider developing a graphical user interface (GUI) for non-technical users for easier script management and execution.
49
-
50
- Medisoft Field Navigation:
51
- Investigate and optimize navigation for fields that Medisoft skips or requires backward navigation to access.
52
-
53
- Insurance Mode Features:
54
- Evaluate the feasibility and implement the use of the F6 search for insurance address verification, enhancing user verification processes.
55
-
56
- Error Handling and Logging:
57
- Implement a check for AHK script execution status, providing feedback or troubleshooting steps if the script encounters issues.
58
- """
59
-
60
25
  def identify_field(header, field_mapping):
61
26
  for medisoft_field, patterns in field_mapping.items():
62
27
  for pattern in patterns:
@@ -223,7 +188,7 @@ class ExecutionState:
223
188
  MediLink_ConfigLoader.log("Constants initialized...")
224
189
 
225
190
  except Exception as e:
226
- print("Failed to load or update configuration: {}".format(e))
191
+ print("MediBot: Failed to load or update configuration: {}".format(e))
227
192
  raise # Re-throwing the exception or using a more sophisticated error handling mechanism might be needed
228
193
  # Handle the exception somehow (e.g., retry, halt, log)??
229
194
 
@@ -261,6 +226,7 @@ if __name__ == "__main__":
261
226
  MediLink_ConfigLoader.log("Pre-processing CSV Data...", level="INFO")
262
227
  MediBot_Preprocessor.preprocess_csv_data(csv_data, e_state.crosswalk)
263
228
  headers = csv_data[0].keys() # Make sure all the headers are in place
229
+ # Patient charges should be added prior to the intake_scan.
264
230
 
265
231
  print("Performing Intake Scan...")
266
232
  MediLink_ConfigLoader.log("Performing Intake Scan...", level="INFO")
@@ -294,9 +260,12 @@ if __name__ == "__main__":
294
260
 
295
261
  if existing_patients:
296
262
  print("\nNOTE: The following patient(s) already EXIST in the system and \n will be excluded from processing:")
263
+ # TODO ... not excluded anymore, because charges may need to be added.
264
+ # So at this point in the processing, we should have already processed the surgery schedules and enriched the data with Charges.
297
265
  for patient_id, patient_name in existing_patients:
298
266
  print("(ID: {0}) {1}".format(patient_id, patient_name))
299
267
  # Update csv_data to exclude existing patients
268
+ # TODO This now has to be updated to handle patients that exist but need new charges added.
300
269
  csv_data = [row for row in csv_data if row[reverse_mapping['Patient ID #2']] in patients_to_process]
301
270
  else:
302
271
  print("\nSelected patient(s) are NEW patients and will be processed.")
@@ -306,6 +275,10 @@ if __name__ == "__main__":
306
275
  else:
307
276
  proceed = input("\nDo you want to proceed with the {} remaining patient(s)? (yes/no): ".format(len(patients_to_process))).lower().strip() in ['yes', 'y']
308
277
 
278
+ # TODO Here is where we need to add the step where we move to MediBot_Charges.
279
+ # The return is an enriched dataset to be picked up by medibot which means we need to return:
280
+ # csv_data, field_mapping, reverse_mapping, and fixed_values.
281
+
309
282
  if proceed:
310
283
 
311
284
  # Would be nice to have some kind of self-test here.
@@ -1,28 +0,0 @@
1
- """
2
- MediBot_Charges.py
3
-
4
- This module provides a helper function for MediBot.py, specifically designed to handle the charge entry process in Medisoft for patients undergoing cataract surgeries. Each patient typically undergoes procedures on both eyes, treated as separate procedures but often requiring price bundling to address patient preferences for consistent billing across both eyes.
5
-
6
- Key Features:
7
- - Handles up to 200 patients in a single batch, with typical usage around 20 patients.
8
- - Implements price bundling logic to ensure that if a patient has multiple procedures (one for each eye), the charges are balanced such that the total cost is evenly spread across both procedures. This approach aligns with patient expectations of receiving consistent charges for each eye.
9
- - Integrates pricing schedules from a configuration file for private insurance, which vary based on the duration of the procedure:
10
- - $450 for 1-15 minutes
11
- - $480 for 16-22 minutes
12
- - $510 for 23-27 minutes
13
- - $540 for 28-37 minutes
14
- - $580 for 37-59 minutes (59 minutes being the maximum allowed duration)
15
- - Special handling for Medicare patients, details of which are pending clarification.
16
-
17
- Limitations:
18
- - The bundling logic for commercial insurance billing is subject to the fulfillment of deductibles and other conditions, which do not necessarily synchronize with the timing of procedures for both eyes. The exact mechanics of this bundling under various conditions remain partially unspecified.
19
-
20
- Usage:
21
- - This module is intended to be used as part of the MediBot system, interfaced through MediBot.py, to automate the data entry and billing process in Medisoft for ophthalmology clinics, specifically those performing cataract surgeries.
22
-
23
- Note:
24
- - The exact implementation details for Medicare patients and the full logic for price bundling under all insurance conditions are yet to be finalized and documented.
25
-
26
- Date:
27
- - 4/16/24
28
- """
@@ -167,6 +167,7 @@ def crosswalk_update(config, crosswalk):
167
167
 
168
168
  # Load incremental mapping data from Z.dat (Patient ID to Insurance Name)
169
169
  # TODO This may be a redundant approach?
170
+ # This is a singular path. This is fine though because any time we process a Z.DAT we'd have the crosswalk incremented.
170
171
  patient_id_to_insurance_name = MediBot_Preprocessor_lib.parse_z_dat(config['MediLink_Config']['Z_DAT_PATH'], config['MediLink_Config'])
171
172
  MediLink_ConfigLoader.log("Parsed Z data...")
172
173
 
File without changes
@@ -20,61 +20,6 @@ try:
20
20
  except ImportError:
21
21
  from MediBot import MediBot_Preprocessor_lib
22
22
 
23
- """
24
- Preprocessing Enhancements
25
- - [X] Preprocess Insurance Policy Numbers and Group Numbers to replace '-' with ''.
26
- - [X] De-duplicate entries in the CSV and only entering the px once even if they show up twice in the file.
27
- - [ ] Implement dynamic field combination in CSV pre-processing for flexibility with various CSV formats.
28
- - [ ] Enhance SSN cleaning logic to handle more variations of sensitive data masking.
29
- - [ ] Optimize script startup and CSV loading to reduce initial latency.
30
-
31
- Data Integrity and Validation
32
- - [ ] Conduct a thorough CSV integrity check before processing to flag potential issues upfront.
33
- - [ ] Implement a mechanism to confirm the accuracy of entered data, potentially through a verification step or summary report.
34
- - [ ] Explore the possibility of integrating direct database queries for existing patient checks to streamline the process.
35
- - [ ] Automate the replacement of spaces with underscores ('_') in last names for Medicare entries.
36
- - [ ] Enhance CSV integrity checks to identify and report potential issues with data format, especially concerning insurance policy numbers and special character handling.
37
-
38
- Known Issues and Bugs
39
- - [ ] Address the handling of '.' and other special characters that may disrupt parsing, especially under Windows XP.
40
- - [ ] Investigate the issue with Excel modifying long policy numbers in the CSV and provide guidance or a workaround.
41
-
42
- Future Work
43
- - [X] Check for PatientID number in La Forma Z to link back to Carol's table for mapping Medisoft insurance name to payerID and payer name and address.
44
- - [X] Check for PatientID to Medisoft custom insurance name mapping in MAPAT.
45
- - [X] Middle Names should all be single letters. Make sure it gets truncated before submitting.
46
- - [ ] 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.
47
- - [ ] Implement logic to verify and match Patient IDs across different files to ensure data integrity before consolidation. (Catching errors between source data)
48
- - [ ] Optimize the preprocessing of surgery dates and diagnosis codes for use in patient billing and scheduling systems.
49
- - [ ] Read Surgery Schedule doc and parse out a Patient ID : Diagnosis Code table.
50
- - [ ] The Minutes & Cancellation data with logic to consolidate into one table in memory.
51
- - [ ] Dynamically list the endpoint for a new Payer ID via API or user interaction to update the crosswalk.json efficiently.
52
- - [ ] Pull listed addresses of insurance from the CSV. (Not really necessary)
53
- - [ ] Retroactively learn Medisoft insurance name and payerID from the provided data sources.
54
-
55
- Development Roadmap for crosswalk_update():
56
- - [X] Automation required for updating the crosswalk.json when new Medisoft insurance is discovered.
57
- - [X] New Medisoft insurances are identified based on the payer ID number.
58
- - [X] Check the existence of the payer ID in crosswalk.json under existing endpoints.
59
- - [X] Facilitate grouping of IDs for insurances like CIGNA with multiple addresses but few payer IDs.
60
- - [X] Retroactive learning based on selected insurances in Medisoft
61
- - [ ] Prompt user via endpoint APIs to add new payer ID to an endpoint if it does not exist.
62
- - [ ] Retain payer IDs without Insurance ID for future assignments.
63
- - [ ] Check for free payer IDs and determine the appropriate endpoint for assignment.
64
- - [ ] Present unrecognized payer IDs with Carol's Insurance Name to users for assignment to Insurance ID. (Try API Call)
65
- - [ ] Integrate API checks to verify payer ID availability and related information.
66
- - [ ] Implement "Fax/Mail or Other" endpoint for unavailable payer IDs.
67
- - [ ] Present user with a list of top insurances for selection based on fuzzy search scores.
68
- - [ ] Establish payer ID to insurance ID relationship based on user selection.
69
- - [ ] Implicitly establish payer ID to endpoint mapping based on user selection.
70
- - [ ] Implement validation mechanisms to prevent incorrect mappings and ensure data integrity.
71
- - [ ] Considerations for extracting insurance addresses (if necessary)
72
- - [ ] Handle better the case where a payer_id doesn't exist (When Carol's CSV doesn't bring the Payer ID).
73
- Maybe ask the user what the payer ID is for that patient? I dont know.
74
- - [ ] TODO (MED) Crosswalk (both initializing and updating) needs to pull AFTER the Preprocessor for Carol's CSV because
75
- all that data lives in-memory and then gets corrections or replacements before being used so we need
76
- the post-correction data to be used to build and update the crosswalk.
77
- """
78
23
  # Load configuration
79
24
  # Should this also take args? Path for ./MediLink needed to be added for this to resolve
80
25
  config, crosswalk = MediLink_ConfigLoader.load_configuration()
@@ -82,10 +27,11 @@ config, crosswalk = MediLink_ConfigLoader.load_configuration()
82
27
  # CSV Preprocessor built for Carol
83
28
  def preprocess_csv_data(csv_data, crosswalk):
84
29
  try:
85
- # Add the "Ins1 Insurance ID" column to the CSV data.
86
- # This initializes the column with empty values for each row.
87
- MediLink_ConfigLoader.log("CSV Pre-processor: Adding 'Ins1 Insurance ID' column to the CSV data...", level="INFO")
88
- MediBot_Preprocessor_lib.add_insurance_id_column(csv_data)
30
+ # Add the "Ins1 Insurance ID" and "Default Diagnosis #1" columns to the CSV data.
31
+ # This initializes the columns with empty values for each row.
32
+ columns_to_add = ['Ins1 Insurance ID', 'Default Diagnosis #1', 'Procedure Code', 'Minutes', 'Amount']
33
+ MediLink_ConfigLoader.log("CSV Pre-processor: Initializing empty columns to the CSV data...", level="INFO")
34
+ MediBot_Preprocessor_lib.add_columns(csv_data, columns_to_add)
89
35
 
90
36
  # Filter out rows without a Patient ID and rows where the Primary Insurance
91
37
  # is 'AETNA', 'AETNA MEDICARE', or 'HUMANA MED HMO'.
@@ -93,13 +39,20 @@ def preprocess_csv_data(csv_data, crosswalk):
93
39
  MediBot_Preprocessor_lib.filter_rows(csv_data)
94
40
 
95
41
  # Convert 'Surgery Date' from string format to datetime objects for sorting purposes.
96
- # Sort the patients by 'Surgery Date' and then by 'Patient Last' name alphabetically.
97
- # Deduplicate patient records based on Patient ID, keeping the entry with the earliest surgery date.
42
+ MediBot_Preprocessor_lib.convert_surgery_date(csv_data)
43
+
98
44
  # Update the CSV data to include only unique patient records.
99
45
  # Re-sort the CSV data after deduplication to ensure the correct order.
46
+ # Sort the patients by 'Surgery Date' and then by 'Patient Last' name alphabetically.
47
+ # Deduplicate patient records based on Patient ID, keeping the entry with the earliest surgery date.
100
48
  MediLink_ConfigLoader.log("CSV Pre-processor: Sorting and de-duplicating patient records...", level="INFO")
101
- MediBot_Preprocessor_lib.convert_surgery_date(csv_data)
102
- MediBot_Preprocessor_lib.sort_and_deduplicate(csv_data)
49
+ MediBot_Preprocessor_lib.sort_and_deduplicate(csv_data)
50
+ # TODO This eventually needs to be handled differently because now we're wanting to handle both surgery dates.
51
+ # Instead of deleting, maybe we need to make a secondary dataset or some kind of flag or isolate here where
52
+ # MediBot knows to skip it when entering the patient data but is ready to put Charges for the second surgery date.
53
+ # MediLink_Scheduler will have a dictionary persist somewhere that would tell us which patients were billed
54
+ # and which haven't been yet. So, if the patient 'exists' in the system, the next quetion is about claims/billing status.
55
+ # Eventually, we really want to get out of Medisoft...
103
56
 
104
57
  # Convert 'Surgery Date' back to string format if needed for further processing.
105
58
  # Combine 'Patient First', 'Patient Middle', and 'Patient Last' into a single 'Patient Name' field.
@@ -117,6 +70,16 @@ def preprocess_csv_data(csv_data, crosswalk):
117
70
  # If the Payer ID is not found in the crosswalk, create a placeholder entry in the crosswalk and mark the row for review.
118
71
  MediLink_ConfigLoader.log("CSV Pre-processor: Populating 'Ins1 Insurance ID' based on Crosswalk...", level="INFO")
119
72
  MediBot_Preprocessor_lib.update_insurance_ids(csv_data, crosswalk)
73
+
74
+ # Enrich the "Default Diagnosis #1" column based on the parsed docx for each row.
75
+ # This needs to handle the different patient dates correctly so we get the right diagnosis code assigned to the right patient on the right date of service.
76
+ # Currently, we've deleted all the second date entries for patients. As long as they exist in the system, they're just deleted.
77
+ MediLink_ConfigLoader.log("CSV Pre-processor: Populating 'Default Diagnosis #1' based on Surgery Schedule and Crosswalk...", level="INFO")
78
+ MediBot_Preprocessor_lib.update_diagnosis_codes(csv_data)
79
+
80
+ # Enrich the procedure code column based on the diagnosis code for each patient.
81
+ MediLink_ConfigLoader.log("CSV Pre-processor: Populating 'Procedure Code' based on Crosswalk...", level="INFO")
82
+ MediBot_Preprocessor_lib.update_procedure_codes(csv_data)
120
83
 
121
84
  except Exception as e:
122
85
  message = "An error occurred while pre-processing CSV data. Please repair the CSV directly and try again: {}".format(e)
@@ -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