medicafe 0.250812.4__py3-none-any.whl → 0.250812.6__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.
@@ -11,6 +11,13 @@ MediLink_ConfigLoader = get_shared_config_loader()
11
11
  import MediLink_DataMgmt
12
12
  import MediLink_Display_Utils
13
13
 
14
+ # Optional import for submission index (duplicate detection)
15
+ try:
16
+ from MediCafe.submission_index import compute_claim_key, find_by_claim_key
17
+ except Exception:
18
+ compute_claim_key = None
19
+ find_by_claim_key = None
20
+
14
21
  # Add parent directory access for MediBot import
15
22
  project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
16
23
  if project_dir not in sys.path:
@@ -184,7 +191,7 @@ def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_map
184
191
  data['insurance_type_source'] = 'DEFAULT_FALLBACK'
185
192
 
186
193
  else:
187
- # Legacy mode (preserve existing behavior exactly)
194
+ # Legacy mode (preserve existing behavior exactly) + always set source
188
195
  MediLink_ConfigLoader.log("Using legacy insurance type enrichment", level="INFO")
189
196
  for data in detailed_patient_data:
190
197
  # FIELD NAME CLARIFICATION: Use 'patient_id' field created by extract_and_suggest_endpoint()
@@ -192,16 +199,33 @@ def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_map
192
199
  patient_id = data.get('patient_id')
193
200
  if patient_id:
194
201
  insurance_type = patient_insurance_type_mapping.get(patient_id, '12') # Default to '12' (PPO/SBR09)
202
+ data['insurance_type'] = insurance_type
203
+ # Mirror enhanced mode semantics for source
204
+ data['insurance_type_source'] = 'MANUAL' if patient_id in patient_insurance_type_mapping else 'DEFAULT'
195
205
  else:
196
206
  # Handle case where patient_id is missing or empty
197
207
  MediLink_ConfigLoader.log("No patient_id found in data record", level="WARNING")
198
208
  insurance_type = '12' # Default when no patient ID available
199
-
200
- data['insurance_type'] = insurance_type
209
+ data['insurance_type'] = insurance_type
210
+ data['insurance_type_source'] = 'DEFAULT_FALLBACK'
201
211
 
202
212
  return detailed_patient_data
203
213
 
204
214
 
215
+ def _normalize_dos_to_iso(mm_dd_yy):
216
+ """Convert date like 'MM-DD-YY' to 'YYYY-MM-DD' safely."""
217
+ try:
218
+ parts = mm_dd_yy.split('-')
219
+ if len(parts) == 3:
220
+ mm, dd, yy = parts
221
+ # Assume 20xx for YY < 50 else 19xx (adjust as needed)
222
+ century = '20' if int(yy) < 50 else '19'
223
+ return "{}-{}-{}".format(century + yy, mm.zfill(2), dd.zfill(2))
224
+ except Exception:
225
+ pass
226
+ return mm_dd_yy
227
+
228
+
205
229
  def extract_and_suggest_endpoint(file_path, config, crosswalk):
206
230
  """
207
231
  Reads a fixed-width file, extracts file details including surgery date, patient ID,
@@ -237,6 +261,13 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
237
261
  insurance_to_id = load_insurance_data_from_mains(config)
238
262
  MediLink_ConfigLoader.log("Insurance data loaded from MAINS. {} insurance providers found.".format(len(insurance_to_id)), level="INFO")
239
263
 
264
+ # Resolve receiptsRoot for duplicate detection (optional)
265
+ try:
266
+ medi_cfg = extract_medilink_config(config)
267
+ receipts_root = medi_cfg.get('local_claims_path', None)
268
+ except Exception:
269
+ receipts_root = None
270
+
240
271
  for personal_info, insurance_info, service_info, service_info_2, service_info_3 in MediLink_DataMgmt.read_fixed_width_data(file_path):
241
272
  # Parse reserved 5-line record: 3 active lines + 2 reserved for future expansion
242
273
  try:
@@ -246,6 +277,7 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
246
277
  parsed_data = MediLink_DataMgmt.parse_fixed_width_data(personal_info, insurance_info, service_info, service_info_2, service_info_3, cfg_for_parse)
247
278
 
248
279
  primary_insurance = parsed_data.get('INAME')
280
+ primary_procedure_code = parsed_data.get('CODEA')
249
281
 
250
282
  # Retrieve the insurance ID associated with the primary insurance
251
283
  insurance_id = insurance_to_id.get(primary_insurance)
@@ -256,7 +288,6 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
256
288
  if insurance_id:
257
289
  for payer_id, payer_data in crosswalk.get('payer_id', {}).items():
258
290
  medisoft_ids = [str(id) for id in payer_data.get('medisoft_id', [])]
259
- # MediLink_ConfigLoader.log("Payer ID: {}, Medisoft IDs: {}".format(payer_id, medisoft_ids))
260
291
  if str(insurance_id) in medisoft_ids:
261
292
  payer_ids.append(payer_id)
262
293
  if payer_ids:
@@ -283,22 +314,52 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
283
314
  else:
284
315
  MediLink_ConfigLoader.log("No suggested endpoint found for payer IDs: {}".format(payer_ids))
285
316
 
317
+ # Normalize DOS for keying
318
+ raw_dos = parsed_data.get('DATE')
319
+ iso_dos = _normalize_dos_to_iso(raw_dos) if raw_dos else ''
320
+
286
321
  # Enrich detailed patient data with additional information and suggested endpoint
287
322
  detailed_data = parsed_data.copy() # Copy parsed_data to avoid modifying the original dictionary
288
323
  detailed_data.update({
289
324
  'file_path': file_path,
290
- # CRITICAL FIELD MAPPING: 'CHART' field from fixed-width file becomes 'patient_id'
291
- # This is the field that enrich_with_insurance_type() will use
292
- 'patient_id': parsed_data.get('CHART'), # <- This is the key field mapping for MediLink flow
325
+ 'patient_id': parsed_data.get('CHART'),
293
326
  'surgery_date': parsed_data.get('DATE'),
327
+ 'surgery_date_iso': iso_dos,
294
328
  'patient_name': ' '.join([parsed_data.get(key, '') for key in ['FIRST', 'MIDDLE', 'LAST']]),
295
329
  'amount': parsed_data.get('AMOUNT'),
296
330
  'primary_insurance': primary_insurance,
331
+ 'primary_procedure_code': primary_procedure_code,
297
332
  'suggested_endpoint': suggested_endpoint
298
333
  })
334
+
335
+ # Compute claim_key (optional)
336
+ claim_key = None
337
+ try:
338
+ if compute_claim_key:
339
+ claim_key = compute_claim_key(
340
+ detailed_data.get('patient_id', ''),
341
+ '', # payer_id not reliably known here
342
+ detailed_data.get('primary_insurance', ''),
343
+ detailed_data.get('surgery_date_iso', ''),
344
+ detailed_data.get('primary_procedure_code', '')
345
+ )
346
+ detailed_data['claim_key'] = claim_key
347
+ except Exception:
348
+ pass
349
+
350
+ # Duplicate candidate flag (optional upstream detection)
351
+ try:
352
+ if find_by_claim_key and receipts_root and claim_key:
353
+ existing = find_by_claim_key(receipts_root, claim_key)
354
+ detailed_data['duplicate_candidate'] = bool(existing)
355
+ else:
356
+ detailed_data['duplicate_candidate'] = False
357
+ except Exception:
358
+ detailed_data['duplicate_candidate'] = False
359
+
299
360
  detailed_patient_data.append(detailed_data)
300
361
 
301
- # Return only the enriched detailed patient data, eliminating the need for a separate summary list
362
+ # Return only the enriched detailed patient data
302
363
  return detailed_patient_data
303
364
 
304
365
 
MediLink/MediLink_UI.py CHANGED
@@ -201,6 +201,8 @@ def select_and_adjust_files(detailed_patient_data, config, crosswalk):
201
201
  )
202
202
  if updated_crosswalk:
203
203
  crosswalk = updated_crosswalk
204
+ # TODO (MEDICARE ROUTING): If original primary was Medicare and crossover failed, prompt to create secondary claim
205
+ # and set claim_type='secondary' with prior_payer fields for the selected patient.
204
206
  else:
205
207
  print("Invalid selection. Keeping the current endpoint.")
206
208
  data['confirmed_endpoint'] = current_effective_endpoint
MediLink/MediLink_Up.py CHANGED
@@ -1,4 +1,15 @@
1
1
  # MediLink_Up.py
2
+ """
3
+ Notes:
4
+ - Duplicate detection relies on a JSONL index under MediLink_Config['receiptsRoot'].
5
+ If 'receiptsRoot' is missing, duplicate checks are skipped with no errors.
6
+ - The claim_key used for deconfliction is practical rather than cryptographic:
7
+ it combines (patient_id if available, else ''), (payer_id or primary_insurance), DOS, and a simple service/procedure indicator.
8
+ In this file-level flow, we approximate with primary_insurance + DOS + file basename for pre-checks.
9
+ Upstream detection now also flags duplicates per patient record using procedure code when available.
10
+ - We do NOT write to the index until a successful submission occurs.
11
+ - All I/O uses ASCII-safe defaults.
12
+ """
2
13
  from datetime import datetime
3
14
  import os, re, subprocess, traceback
4
15
  try:
@@ -22,6 +33,18 @@ try:
22
33
  except ImportError:
23
34
  api_core = None
24
35
 
36
+ # Import submission index helpers (XP-safe JSONL)
37
+ try:
38
+ from MediCafe.submission_index import (
39
+ compute_claim_key,
40
+ find_by_claim_key,
41
+ append_submission_record
42
+ )
43
+ except Exception:
44
+ compute_claim_key = None
45
+ find_by_claim_key = None
46
+ append_submission_record = None
47
+
25
48
  # Pre-compile regex patterns for better performance
26
49
  GS_PATTERN = re.compile(r'GS\*HC\*[^*]*\*[^*]*\*([0-9]{8})\*([0-9]{4})')
27
50
  SE_PATTERN = re.compile(r'SE\*\d+\*\d{4}~')
@@ -60,12 +83,10 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
60
83
  """
61
84
  Submits claims for each endpoint, either via WinSCP or API, based on configuration settings.
62
85
 
63
- Parameters:
64
- - detailed_patient_data_grouped_by_endpoint: Dictionary with endpoints as keys and lists of patient data as values.
65
- - config: Configuration settings loaded from a JSON file.
66
-
67
- Returns:
68
- - None
86
+ Deconfliction (XP-safe):
87
+ - If JSONL index helpers are available and receiptsRoot is configured, compute a claim_key per 837p file
88
+ and skip submit if index already contains that key (duplicate protection).
89
+ - After a successful submission, append an index record.
69
90
  """
70
91
  # Normalize configuration for safe nested access
71
92
  if not isinstance(config, dict):
@@ -73,7 +94,6 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
73
94
  config, _ = load_configuration()
74
95
  except Exception:
75
96
  config = {}
76
- # Ensure cfg is always a dict to avoid NoneType.get errors
77
97
  if isinstance(config, dict):
78
98
  cfg_candidate = config.get('MediLink_Config')
79
99
  if isinstance(cfg_candidate, dict):
@@ -83,6 +103,9 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
83
103
  else:
84
104
  cfg = {}
85
105
 
106
+ # Resolve receipts folder for index (use same path as receipts)
107
+ receipts_root = cfg.get('local_claims_path', None)
108
+
86
109
  # Accumulate submission results
87
110
  submission_results = {}
88
111
 
@@ -113,10 +136,8 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
113
136
  method = cfg.get('endpoints', {}).get(endpoint, {}).get('submission_method', 'winscp')
114
137
  except Exception as e:
115
138
  log("[submit_claims] Error deriving submission method for endpoint {}: {}".format(endpoint, e), level="ERROR")
116
- # Absolute fallback if cfg was unexpectedly not a dict
117
139
  method = 'winscp'
118
140
 
119
- # Attempt submission to each endpoint
120
141
  if True: #confirm_transmission({endpoint: patients_data}): # Confirm transmission to each endpoint with detailed overview
121
142
  if check_internet_connection():
122
143
  client = get_api_client()
@@ -157,13 +178,44 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
157
178
  pass
158
179
  raise
159
180
  if converted_files:
160
- if method == 'winscp':
181
+ # Deconfliction pre-check per file if helpers available
182
+ filtered_files = []
183
+ for file_path in converted_files:
184
+ if compute_claim_key and find_by_claim_key and receipts_root:
185
+ try:
186
+ # Compute a simple service hash from file path (can be improved later)
187
+ service_hash = os.path.basename(file_path)
188
+ # Attempt to parse minimal patient_id and DOS from filename if available
189
+ # For now, rely on patient data embedded in file content via parse_837p_file
190
+ patients, _ = parse_837p_file(file_path)
191
+ # If we cannot compute a stable key, skip deconflict
192
+ if patients:
193
+ # Use first patient for keying; future improvement: per-service keys
194
+ p = patients[0]
195
+ patient_id = "" # unknown at this stage (facesheet may not contain chart)
196
+ payer_id = ""
197
+ primary_insurance = p.get('insurance_name', '')
198
+ dos = p.get('service_date', '')
199
+ claim_key = compute_claim_key(patient_id, payer_id, primary_insurance, dos, service_hash)
200
+ existing = find_by_claim_key(receipts_root, claim_key)
201
+ if existing:
202
+ print("Duplicate detected; skipping file: {}".format(file_path))
203
+ continue
204
+ except Exception:
205
+ # Fail open (do not block submission)
206
+ pass
207
+ filtered_files.append(file_path)
208
+
209
+ if not filtered_files:
210
+ print("All files skipped as duplicates for endpoint {}.".format(endpoint))
211
+ submission_results[endpoint] = {}
212
+ elif method == 'winscp':
161
213
  # Transmit files via WinSCP
162
214
  try:
163
215
  operation_type = "upload"
164
216
  endpoint_cfg = cfg.get('endpoints', {}).get(endpoint, {})
165
217
  local_claims_path = cfg.get('local_claims_path', '.')
166
- transmission_result = operate_winscp(operation_type, converted_files, endpoint_cfg, local_claims_path, config)
218
+ transmission_result = operate_winscp(operation_type, filtered_files, endpoint_cfg, local_claims_path, config)
167
219
  success_dict = handle_transmission_result(transmission_result, config, operation_type, method)
168
220
  submission_results[endpoint] = success_dict
169
221
  except FileNotFoundError as e:
@@ -179,7 +231,7 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
179
231
  # Transmit files via API
180
232
  try:
181
233
  api_responses = []
182
- for file_path in converted_files:
234
+ for file_path in filtered_files:
183
235
  with open(file_path, 'r') as file:
184
236
  # Optimize string operations by doing replacements in one pass
185
237
  x12_request_data = file.read().replace('\n', '').replace('\r', '').strip()
@@ -217,6 +269,43 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
217
269
 
218
270
  # Build and display receipt
219
271
  build_and_display_receipt(submission_results, config)
272
+
273
+ # Append index records for successes
274
+ try:
275
+ if append_submission_record and isinstance(submission_results, dict):
276
+ # Resolve receipts root
277
+ if isinstance(config, dict):
278
+ _cfg2 = config.get('MediLink_Config')
279
+ cfg2 = _cfg2 if isinstance(_cfg2, dict) else config
280
+ else:
281
+ cfg2 = {}
282
+ receipts_root2 = cfg2.get('local_claims_path', None)
283
+ if receipts_root2:
284
+ for endpoint, files in submission_results.items():
285
+ for file_path, result in files.items():
286
+ try:
287
+ status, message = result
288
+ if status:
289
+ patients, submitted_at = parse_837p_file(file_path)
290
+ # Take first patient for keying; improve later for per-service handling
291
+ p = patients[0] if patients else {}
292
+ claim_key = compute_claim_key("", "", p.get('insurance_name', ''), p.get('service_date', ''), os.path.basename(file_path))
293
+ record = {
294
+ 'claim_key': claim_key,
295
+ 'patient_id': "",
296
+ 'payer_id': "",
297
+ 'primary_insurance': p.get('insurance_name', ''),
298
+ 'dos': p.get('service_date', ''),
299
+ 'endpoint': endpoint,
300
+ 'submitted_at': submitted_at or datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
301
+ 'receipt_file': os.path.basename(file_path),
302
+ 'status': 'success'
303
+ }
304
+ append_submission_record(receipts_root2, record)
305
+ except Exception:
306
+ continue
307
+ except Exception:
308
+ pass
220
309
 
221
310
  print("Claim submission process completed.\n")
222
311
 
MediLink/MediLink_main.py CHANGED
@@ -54,6 +54,35 @@ if PERFORMANCE_LOGGING:
54
54
  # - XP note: default to console prompts; optional UI later.
55
55
  # This already happens when MediLink is opened.
56
56
 
57
+ def _tools_menu(config, medi):
58
+ """Low-use maintenance tools submenu."""
59
+ while True:
60
+ print("\nMaintenance Tools:")
61
+ options = [
62
+ "Rebuild submission index now",
63
+ "Back"
64
+ ]
65
+ MediLink_UI.display_menu(options)
66
+ choice = MediLink_UI.get_user_choice().strip()
67
+ if choice == '1':
68
+ receipts_root = medi.get('local_claims_path', None)
69
+ if not receipts_root:
70
+ print("No receipts folder configured (local_claims_path missing).")
71
+ continue
72
+ try:
73
+ from MediCafe.submission_index import build_initial_index
74
+ receipts_root = os.path.normpath(receipts_root)
75
+ print("Rebuilding submission index... (this may take a while)")
76
+ count = build_initial_index(receipts_root)
77
+ print("Index rebuild complete. Indexed {} records.".format(count))
78
+ except Exception as e:
79
+ print("Index rebuild error: {}".format(e))
80
+ elif choice == '2':
81
+ break
82
+ else:
83
+ MediLink_UI.display_invalid_choice()
84
+
85
+
57
86
  def main_menu():
58
87
  """
59
88
  Initializes the main menu loop and handles the overall program flow,
@@ -125,6 +154,16 @@ def main_menu():
125
154
  if PERFORMANCE_LOGGING:
126
155
  print("Path normalization completed in {:.2f} seconds".format(path_norm_end - path_norm_start))
127
156
 
157
+ # NEW: Submission index upkeep (XP-safe, inline)
158
+ try:
159
+ receipts_root = medi.get('local_claims_path', None)
160
+ if receipts_root:
161
+ from MediCafe.submission_index import ensure_submission_index
162
+ ensure_submission_index(os.path.normpath(receipts_root))
163
+ except Exception:
164
+ # Silent failure - do not block menu
165
+ pass
166
+
128
167
  # Detect files and determine if a new file is flagged.
129
168
  file_detect_start = time.time()
130
169
  if PERFORMANCE_LOGGING:
@@ -142,7 +181,7 @@ def main_menu():
142
181
 
143
182
  while True:
144
183
  # Define static menu options for consistent numbering
145
- options = ["Check for new remittances", "Submit claims", "Exit"]
184
+ options = ["Check for new remittances", "Submit claims", "Exit", "Tools"]
146
185
 
147
186
  # Display the menu options.
148
187
  menu_display_start = time.time()
@@ -193,6 +232,22 @@ def main_menu():
193
232
  elif choice == '3':
194
233
  MediLink_UI.display_exit_message()
195
234
  break
235
+ elif choice == '4':
236
+ _tools_menu(config, medi)
237
+ elif choice.lower() == 'tools:index':
238
+ # Optional maintenance: rebuild submission index now (synchronous)
239
+ try:
240
+ receipts_root = medi.get('local_claims_path', None)
241
+ if not receipts_root:
242
+ print("No receipts folder configured.")
243
+ continue
244
+ from MediCafe.submission_index import build_initial_index
245
+ receipts_root = os.path.normpath(receipts_root)
246
+ print("Rebuilding submission index... (this may take a while)")
247
+ count = build_initial_index(receipts_root)
248
+ print("Index rebuild complete. Indexed {} records.".format(count))
249
+ except Exception as e:
250
+ print("Index rebuild error: {}".format(e))
196
251
  else:
197
252
  # Display an error message if the user's choice does not match any valid option.
198
253
  MediLink_UI.display_invalid_choice()
@@ -229,6 +284,56 @@ def handle_submission(detailed_patient_data, config, crosswalk):
229
284
  # Update crosswalk reference if it was modified
230
285
  if updated_crosswalk:
231
286
  crosswalk = updated_crosswalk
287
+
288
+ # Upstream duplicate prompt: flag and allow user to exclude duplicates before submission
289
+ try:
290
+ medi_cfg = extract_medilink_config(config)
291
+ receipts_root = medi_cfg.get('local_claims_path', None)
292
+ if receipts_root:
293
+ try:
294
+ from MediCafe.submission_index import compute_claim_key, find_by_claim_key
295
+ except Exception:
296
+ compute_claim_key = None
297
+ find_by_claim_key = None
298
+ if compute_claim_key and find_by_claim_key:
299
+ for data in adjusted_data:
300
+ try:
301
+ # Use precomputed claim_key when available, else build it
302
+ claim_key = data.get('claim_key', None)
303
+ if not claim_key:
304
+ claim_key = compute_claim_key(
305
+ data.get('patient_id', ''),
306
+ '',
307
+ data.get('primary_insurance', ''),
308
+ data.get('surgery_date_iso', data.get('surgery_date', '')),
309
+ data.get('primary_procedure_code', '')
310
+ )
311
+ existing = find_by_claim_key(receipts_root, claim_key) if claim_key else None
312
+ if existing:
313
+ # Show informative prompt
314
+ print("\nPotential duplicate detected:")
315
+ print("- Patient: {} ({})".format(data.get('patient_name', ''), data.get('patient_id', '')))
316
+ print("- DOS: {} | Insurance: {} | Proc: {}".format(
317
+ data.get('surgery_date', ''),
318
+ data.get('primary_insurance', ''),
319
+ data.get('primary_procedure_code', '')
320
+ ))
321
+ print("- Prior submission: {} via {} (receipt: {})".format(
322
+ existing.get('submitted_at', 'unknown'),
323
+ existing.get('endpoint', 'unknown'),
324
+ existing.get('receipt_file', 'unknown')
325
+ ))
326
+ ans = input("Submit anyway? (Y/N): ").strip().lower()
327
+ if ans not in ['y', 'yes']:
328
+ data['exclude_from_submission'] = True
329
+ except Exception:
330
+ # Do not block flow on errors
331
+ continue
332
+ except Exception:
333
+ pass
334
+
335
+ # Filter out excluded items prior to confirmation and submission
336
+ adjusted_data = [d for d in adjusted_data if not d.get('exclude_from_submission')]
232
337
 
233
338
  # Confirm all remaining suggested endpoints.
234
339
  confirmed_data = MediLink_DataMgmt.confirm_all_suggested_endpoints(adjusted_data)
@@ -240,11 +345,6 @@ def handle_submission(detailed_patient_data, config, crosswalk):
240
345
  if MediLink_Up.check_internet_connection():
241
346
  # Submit claims if internet connectivity is confirmed.
242
347
  _ = MediLink_Up.submit_claims(organized_data, config, crosswalk)
243
- # TODO submit_claims will have a receipt return in the future.
244
- # PLAN: submit_claims should return a structure like:
245
- # {'endpoint': ep, 'files': [{'path': p, 'status': 'ok'|'error', 'receipt_id': '...', 'timestamp': ...}], 'errors': [...]}
246
- # Callers can log and optionally display the receipt IDs or open an acknowledgment view.
247
- # Backward-compatibility: if None/empty is returned, proceed as today.
248
348
  else:
249
349
  # Notify the user of an internet connection error.
250
350
  print("Internet connection error. Please ensure you're connected and try again.")
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.1
2
+ Name: medicafe
3
+ Version: 0.250812.6
4
+ Summary: MediCafe
5
+ Home-page: https://github.com/katanada2/MediCafe
6
+ Author: Daniel Vidaud
7
+ Author-email: daniel@personalizedtransformation.com
8
+ License: MIT
9
+ Project-URL: Source, https://github.com/katanada2/MediCafe
10
+ Project-URL: Bug Tracker, https://github.com/katanada2/MediCafe/issues
11
+ Keywords: medicafe medibot medilink medisoft automation healthcare claims
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.4
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.4, <3.5
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: requests ==2.21.0
20
+ Requires-Dist: argparse ==1.4.0
21
+ Requires-Dist: tqdm ==4.14.0
22
+ Requires-Dist: python-docx ==0.8.11
23
+ Requires-Dist: PyYAML ==5.2
24
+ Requires-Dist: chardet ==3.0.4
25
+ Requires-Dist: cffi ==1.8.2
26
+ Requires-Dist: msal ==1.26.0
27
+ Requires-Dist: numpy ==1.11.3 ; platform_python_implementation != "CPython" or sys_platform != "win32" or python_version > "3.5"
28
+ Requires-Dist: pandas ==0.20.0 ; platform_python_implementation != "CPython" or sys_platform != "win32" or python_version > "3.5"
29
+ Requires-Dist: lxml ==4.2.0 ; platform_python_implementation != "CPython" or sys_platform != "win32" or python_version > "3.5"
30
+ Requires-Dist: numpy ==1.11.3 ; platform_python_implementation == "CPython" and sys_platform == "win32" and python_version <= "3.5" and extra == "binary"
31
+ Requires-Dist: pandas ==0.20.0 ; platform_python_implementation == "CPython" and sys_platform == "win32" and python_version <= "3.5" and extra == "binary"
32
+ Requires-Dist: lxml ==4.2.0 ; platform_python_implementation == "CPython" and sys_platform == "win32" and python_version <= "3.5" and extra == "binary"
33
+
34
+ # MediCafe
35
+
36
+ MediCafe is a small toolkit that helps automate common Medisoft admin tasks. It has two parts:
37
+
38
+ - MediBot: automates routine data handling
39
+ - MediLink: moves claims and responses between payers and your system
40
+
41
+ The focus is simple: reduce manual steps, add reliable checks, and keep logs that make issues easy to trace.
42
+
43
+ ## Features
44
+ - Command-line entry point: `medicafe`
45
+ - Basic claim routing and status checks
46
+ - Lightweight utilities for importing, validation, and file handling
47
+ - Works on older Python 3.4 installs and modern environments
48
+
49
+ ## Install
50
+ Use pip:
51
+
52
+ ```
53
+ pip install medicafe
54
+ ```
55
+
56
+ If you are on a system with managed Python, you may need a virtual environment:
57
+
58
+ ```
59
+ python3 -m venv .venv
60
+ . .venv/bin/activate
61
+ pip install medicafe
62
+ ```
63
+
64
+ ## Quick start
65
+ Run the main entry point:
66
+
67
+ ```
68
+ medicafe --help
69
+ ```
70
+
71
+ Or from Python:
72
+
73
+ ```
74
+ python3 -m MediCafe --help
75
+ ```
76
+
77
+ Common tasks:
78
+ - Download payer emails
79
+ - Submit or check claim status
80
+ - Run MediLink workflows
81
+
82
+ ## Compatibility
83
+ - Python: 3.4+ (tested with legacy constraints), also runs on newer Python
84
+ - OS: Windows or Linux
85
+
86
+ ## Project goals
87
+ - Keep the code straightforward and readable
88
+ - Respect older environments where needed
89
+ - Fail with clear, actionable messages
90
+
91
+ ## License
92
+ MIT License. See `LICENSE`.
93
+
94
+ ## Support
95
+ This is community-supported software. Open an issue on the project page if you run into a problem or have a small, concrete request. Please include your Python version, OS, and the exact command that failed.