medicafe 0.250711.0__py3-none-any.whl → 0.250720.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_Crosswalk_Library.py +109 -1
- MediLink/MediLink_837p_cob_library.py +862 -0
- MediLink/MediLink_837p_encoder.py +40 -0
- MediLink/MediLink_837p_encoder_library.py +54 -6
- MediLink/MediLink_API_v3.py +24 -5
- MediLink/MediLink_ClaimStatus.py +4 -3
- MediLink/MediLink_Deductible.py +354 -118
- MediLink/MediLink_Deductible_Validator.py +440 -0
- MediLink/MediLink_GraphQL.py +40 -4
- MediLink/test_cob_library.py +436 -0
- MediLink/test_validation.py +127 -0
- {medicafe-0.250711.0.dist-info → medicafe-0.250720.0.dist-info}/METADATA +1 -1
- {medicafe-0.250711.0.dist-info → medicafe-0.250720.0.dist-info}/RECORD +16 -12
- {medicafe-0.250711.0.dist-info → medicafe-0.250720.0.dist-info}/LICENSE +0 -0
- {medicafe-0.250711.0.dist-info → medicafe-0.250720.0.dist-info}/WHEEL +0 -0
- {medicafe-0.250711.0.dist-info → medicafe-0.250720.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
# MediLink_837p_cob_library.py
|
|
2
|
+
"""
|
|
3
|
+
837P Coordination of Benefits (COB) Enhancement Library
|
|
4
|
+
|
|
5
|
+
This module provides enhanced 837P generation capabilities for:
|
|
6
|
+
- Medicare as primary payer (SBR09 = MB/MA)
|
|
7
|
+
- Secondary payer claims with embedded COB adjudication
|
|
8
|
+
- Optional attachment references (PWK)
|
|
9
|
+
- 835-derived adjudication embedding
|
|
10
|
+
|
|
11
|
+
All functions are currently implemented as placeholder comments to preserve
|
|
12
|
+
existing production functionality while providing clear implementation guidance.
|
|
13
|
+
|
|
14
|
+
IMPLEMENTATION STRATEGY:
|
|
15
|
+
1. All functions are commented placeholders to avoid disrupting production code
|
|
16
|
+
2. Detailed implementation guidance is provided in comments
|
|
17
|
+
3. Integration points are marked with TODO comments in the main encoder
|
|
18
|
+
4. Validation logic is included for claim integrity and compliance
|
|
19
|
+
5. Medicare-specific payer types (MB/MA) are properly handled
|
|
20
|
+
6. 835 data extraction and validation is supported
|
|
21
|
+
7. COB loops (2320, 2330B, 2330C, 2430) are fully specified
|
|
22
|
+
|
|
23
|
+
Key Implementation Notes:
|
|
24
|
+
- Medicare Part B uses SBR09 = "MB"
|
|
25
|
+
- Medicare Advantage uses SBR09 = "MA"
|
|
26
|
+
- Secondary claims require SBR01 = "S" and proper COB loops
|
|
27
|
+
- 835 data integration requires validation of remittance dates and amounts
|
|
28
|
+
- SNIP validation level 3+ recommended for COB claims
|
|
29
|
+
- Total paid validation: AMT*D in Loop 2320 must match sum of SVD02 values
|
|
30
|
+
- CLM05-3 must be "1" (original) for secondary claims unless replacement logic applies
|
|
31
|
+
|
|
32
|
+
SEGMENT ORDER CONSTRAINTS:
|
|
33
|
+
- 1000A: Submitter
|
|
34
|
+
- 1000B: Receiver
|
|
35
|
+
- 2000A–2010AA: Billing provider
|
|
36
|
+
- 2000B–2010BA/BB: Subscriber and payer
|
|
37
|
+
- 2300: Claim
|
|
38
|
+
- 2320: Other subscriber info (COB)
|
|
39
|
+
- 2330B: Prior payer
|
|
40
|
+
- 2400: Service line
|
|
41
|
+
- 2430: Line-level COB (if applicable)
|
|
42
|
+
|
|
43
|
+
VALIDATION REQUIREMENTS:
|
|
44
|
+
- SBR09 required if SBR01 = "S"
|
|
45
|
+
- NM1*PR in 2330B must match Medicare Payer ID when applicable
|
|
46
|
+
- CLM segment must reflect consistent claim frequency and COB type
|
|
47
|
+
- 835-derived data must contain remittance date and paid amount per service
|
|
48
|
+
- Multi-payer COB handling must be configured appropriately
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from datetime import datetime
|
|
52
|
+
import sys, os
|
|
53
|
+
from MediLink import MediLink_ConfigLoader
|
|
54
|
+
|
|
55
|
+
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
56
|
+
if project_dir not in sys.path:
|
|
57
|
+
sys.path.append(project_dir)
|
|
58
|
+
|
|
59
|
+
# Safe import strategy to avoid circular dependencies
|
|
60
|
+
def safe_import_encoder_functions():
|
|
61
|
+
"""
|
|
62
|
+
Safely imports required functions from the encoder library.
|
|
63
|
+
Uses dynamic imports to avoid circular dependencies while maintaining functionality.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
- Dictionary containing imported functions or None if import fails
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
# Dynamic import to avoid circular dependency
|
|
70
|
+
from MediLink_837p_encoder_library import convert_date_format
|
|
71
|
+
return {
|
|
72
|
+
'convert_date_format': convert_date_format
|
|
73
|
+
}
|
|
74
|
+
except ImportError as e:
|
|
75
|
+
# Fallback implementation for convert_date_format
|
|
76
|
+
MediLink_ConfigLoader.log("Warning: Could not import encoder functions: {}".format(e), level="WARNING")
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# Initialize encoder functions with fallback
|
|
80
|
+
_encoder_functions = safe_import_encoder_functions()
|
|
81
|
+
|
|
82
|
+
def get_convert_date_format():
|
|
83
|
+
"""
|
|
84
|
+
Safely gets the convert_date_format function with fallback implementation.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
- convert_date_format function or fallback implementation
|
|
88
|
+
"""
|
|
89
|
+
if _encoder_functions and 'convert_date_format' in _encoder_functions:
|
|
90
|
+
return _encoder_functions['convert_date_format']
|
|
91
|
+
else:
|
|
92
|
+
# Fallback implementation
|
|
93
|
+
def fallback_convert_date_format(date_str):
|
|
94
|
+
"""Fallback date format conversion function"""
|
|
95
|
+
try:
|
|
96
|
+
# Parse the input date string into a datetime object
|
|
97
|
+
input_format = "%m-%d-%Y" if len(date_str) == 10 else "%m-%d-%y"
|
|
98
|
+
date_obj = datetime.strptime(date_str, input_format)
|
|
99
|
+
# Format the datetime object into the desired output format
|
|
100
|
+
return date_obj.strftime("%Y%m%d")
|
|
101
|
+
except (ValueError, TypeError):
|
|
102
|
+
# Return original string if conversion fails
|
|
103
|
+
return date_str
|
|
104
|
+
return fallback_convert_date_format
|
|
105
|
+
|
|
106
|
+
def create_2320_other_subscriber_segments(patient_data, config, crosswalk):
|
|
107
|
+
"""
|
|
108
|
+
Creates Loop 2320 segments for other subscriber information in COB scenarios.
|
|
109
|
+
|
|
110
|
+
Required segments:
|
|
111
|
+
- SBR: Secondary payer relationship and responsibility
|
|
112
|
+
- AMT: AMT*D – Total amount paid by Medicare or other primary
|
|
113
|
+
- CAS: Patient liability (CO/PR adjustments)
|
|
114
|
+
- OI: Other insurance coverage indicator
|
|
115
|
+
|
|
116
|
+
Parameters:
|
|
117
|
+
- patient_data: Dictionary containing patient and COB information
|
|
118
|
+
- config: Configuration settings
|
|
119
|
+
- crosswalk: Crosswalk data for payer mapping
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
- List of 2320 loop segments
|
|
123
|
+
"""
|
|
124
|
+
segments = []
|
|
125
|
+
|
|
126
|
+
# Determine if this is a secondary claim
|
|
127
|
+
is_secondary = patient_data.get('claim_type', 'primary') == 'secondary'
|
|
128
|
+
|
|
129
|
+
if is_secondary:
|
|
130
|
+
# SBR segment for secondary payer
|
|
131
|
+
responsibility_code = "S" # Secondary
|
|
132
|
+
insurance_type = determine_medicare_payer_type(patient_data, config)
|
|
133
|
+
if not insurance_type:
|
|
134
|
+
insurance_type = patient_data.get('insurance_type', '18') # Default to Medicare
|
|
135
|
+
|
|
136
|
+
sbr_segment = "SBR*{}*18*******{}~".format(responsibility_code, insurance_type)
|
|
137
|
+
segments.append(sbr_segment)
|
|
138
|
+
|
|
139
|
+
# AMT*D segment for total amount paid by primary
|
|
140
|
+
total_paid = patient_data.get('primary_paid_amount', '0.00')
|
|
141
|
+
amt_segment = "AMT*D*{}~".format(total_paid)
|
|
142
|
+
segments.append(amt_segment)
|
|
143
|
+
|
|
144
|
+
# CAS segments for patient liability adjustments
|
|
145
|
+
cas_adjustments = patient_data.get('cas_adjustments', [])
|
|
146
|
+
for adjustment in cas_adjustments:
|
|
147
|
+
cas_segment = "CAS*{}*{}*{}~".format(
|
|
148
|
+
adjustment.get('group', 'CO'),
|
|
149
|
+
adjustment.get('reason', ''),
|
|
150
|
+
adjustment.get('amount', '0.00')
|
|
151
|
+
)
|
|
152
|
+
segments.append(cas_segment)
|
|
153
|
+
|
|
154
|
+
# OI segment for other insurance coverage
|
|
155
|
+
oi_segment = "OI***Y***Y~"
|
|
156
|
+
segments.append(oi_segment)
|
|
157
|
+
|
|
158
|
+
return segments
|
|
159
|
+
|
|
160
|
+
def create_2330B_prior_payer_segments(patient_data, config, crosswalk):
|
|
161
|
+
"""
|
|
162
|
+
Creates Loop 2330B segments for prior payer information.
|
|
163
|
+
|
|
164
|
+
Required segments:
|
|
165
|
+
- NM1: Prior payer name and ID (e.g., Medicare = 00850)
|
|
166
|
+
- N3: (optional) Address line 1 and 2
|
|
167
|
+
- N4: (optional) City/State/ZIP
|
|
168
|
+
- REF: (optional) Reference ID (e.g., prior auth)
|
|
169
|
+
|
|
170
|
+
Parameters:
|
|
171
|
+
- patient_data: Dictionary containing prior payer information
|
|
172
|
+
- config: Configuration settings
|
|
173
|
+
- crosswalk: Crosswalk data for payer mapping
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
- List of 2330B loop segments
|
|
177
|
+
"""
|
|
178
|
+
segments = []
|
|
179
|
+
|
|
180
|
+
# Get prior payer information
|
|
181
|
+
prior_payer_name = patient_data.get('prior_payer_name', 'MEDICARE')
|
|
182
|
+
prior_payer_id = patient_data.get('prior_payer_id', '00850')
|
|
183
|
+
|
|
184
|
+
# NM1 segment for prior payer
|
|
185
|
+
nm1_segment = "NM1*PR*2*{}*****PI*{}~".format(prior_payer_name, prior_payer_id)
|
|
186
|
+
segments.append(nm1_segment)
|
|
187
|
+
|
|
188
|
+
# Optional N3 segment for address
|
|
189
|
+
if patient_data.get('prior_payer_address'):
|
|
190
|
+
n3_segment = "N3*{}*{}~".format(
|
|
191
|
+
patient_data.get('prior_payer_address', ''),
|
|
192
|
+
patient_data.get('prior_payer_address2', '')
|
|
193
|
+
)
|
|
194
|
+
segments.append(n3_segment)
|
|
195
|
+
|
|
196
|
+
# Optional N4 segment for city/state/zip
|
|
197
|
+
if patient_data.get('prior_payer_city'):
|
|
198
|
+
n4_segment = "N4*{}*{}*{}~".format(
|
|
199
|
+
patient_data.get('prior_payer_city', ''),
|
|
200
|
+
patient_data.get('prior_payer_state', ''),
|
|
201
|
+
patient_data.get('prior_payer_zip', '')
|
|
202
|
+
)
|
|
203
|
+
segments.append(n4_segment)
|
|
204
|
+
|
|
205
|
+
# Optional REF segment for reference ID
|
|
206
|
+
if patient_data.get('prior_auth_number'):
|
|
207
|
+
ref_segment = "REF*G1*{}~".format(patient_data.get('prior_auth_number'))
|
|
208
|
+
segments.append(ref_segment)
|
|
209
|
+
|
|
210
|
+
return segments
|
|
211
|
+
|
|
212
|
+
def create_2430_service_line_cob_segments(patient_data, config, crosswalk):
|
|
213
|
+
"""
|
|
214
|
+
Creates Loop 2430 segments for service line COB information.
|
|
215
|
+
|
|
216
|
+
Required segments (when service-level adjudication exists):
|
|
217
|
+
- SVD: Paid amount per service line (from 835 SVC03)
|
|
218
|
+
- CAS: Line-level adjustments (from 835 CAS segments)
|
|
219
|
+
- DTP: DTP*573 – Adjudication date per service
|
|
220
|
+
|
|
221
|
+
Parameters:
|
|
222
|
+
- patient_data: Dictionary containing service line and adjudication data
|
|
223
|
+
- config: Configuration settings
|
|
224
|
+
- crosswalk: Crosswalk data for service mapping
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
- List of 2430 loop segments
|
|
228
|
+
"""
|
|
229
|
+
segments = []
|
|
230
|
+
|
|
231
|
+
# Get service line adjudication data
|
|
232
|
+
service_adjudications = patient_data.get('service_adjudications', [])
|
|
233
|
+
|
|
234
|
+
for service in service_adjudications:
|
|
235
|
+
# SVD segment for service line paid amount
|
|
236
|
+
svd_segment = "SVD*{}*{}*{}*{}~".format(
|
|
237
|
+
service.get('payer_id', '00850'),
|
|
238
|
+
service.get('paid_amount', '0.00'),
|
|
239
|
+
service.get('revenue_code', ''),
|
|
240
|
+
service.get('units', '1')
|
|
241
|
+
)
|
|
242
|
+
segments.append(svd_segment)
|
|
243
|
+
|
|
244
|
+
# CAS segments for line-level adjustments
|
|
245
|
+
line_adjustments = service.get('adjustments', [])
|
|
246
|
+
for adjustment in line_adjustments:
|
|
247
|
+
cas_segment = "CAS*{}*{}*{}~".format(
|
|
248
|
+
adjustment.get('group', 'CO'),
|
|
249
|
+
adjustment.get('reason', ''),
|
|
250
|
+
adjustment.get('amount', '0.00')
|
|
251
|
+
)
|
|
252
|
+
segments.append(cas_segment)
|
|
253
|
+
|
|
254
|
+
# DTP*573 segment for adjudication date
|
|
255
|
+
if service.get('adjudication_date'):
|
|
256
|
+
convert_date = get_convert_date_format()
|
|
257
|
+
dtp_segment = "DTP*573*D8*{}~".format(
|
|
258
|
+
convert_date(service.get('adjudication_date'))
|
|
259
|
+
)
|
|
260
|
+
segments.append(dtp_segment)
|
|
261
|
+
|
|
262
|
+
return segments
|
|
263
|
+
|
|
264
|
+
def create_2330C_other_subscriber_name_segments(patient_data, config, crosswalk):
|
|
265
|
+
"""
|
|
266
|
+
Creates Loop 2330C segments when patient is not the subscriber.
|
|
267
|
+
|
|
268
|
+
Required segments:
|
|
269
|
+
- NM1*IL: Other Subscriber Name
|
|
270
|
+
- N3/N4: (optional) Address
|
|
271
|
+
- DMG: (optional) Date of birth / gender
|
|
272
|
+
|
|
273
|
+
Parameters:
|
|
274
|
+
- patient_data: Dictionary containing other subscriber information
|
|
275
|
+
- config: Configuration settings
|
|
276
|
+
- crosswalk: Crosswalk data for subscriber mapping
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
- List of 2330C loop segments
|
|
280
|
+
"""
|
|
281
|
+
segments = []
|
|
282
|
+
|
|
283
|
+
# Check if patient is different from subscriber
|
|
284
|
+
if patient_data.get('patient_is_subscriber', True) == False:
|
|
285
|
+
# NM1*IL segment for other subscriber
|
|
286
|
+
nm1_segment = "NM1*IL*1*{}*{}*{}***MI*{}~".format(
|
|
287
|
+
patient_data.get('subscriber_last_name', ''),
|
|
288
|
+
patient_data.get('subscriber_first_name', ''),
|
|
289
|
+
patient_data.get('subscriber_middle_name', ''),
|
|
290
|
+
patient_data.get('subscriber_policy_number', '')
|
|
291
|
+
)
|
|
292
|
+
segments.append(nm1_segment)
|
|
293
|
+
|
|
294
|
+
# Optional N3 segment for address
|
|
295
|
+
if patient_data.get('subscriber_address'):
|
|
296
|
+
n3_segment = "N3*{}*{}~".format(
|
|
297
|
+
patient_data.get('subscriber_address', ''),
|
|
298
|
+
patient_data.get('subscriber_address2', '')
|
|
299
|
+
)
|
|
300
|
+
segments.append(n3_segment)
|
|
301
|
+
|
|
302
|
+
# Optional N4 segment for city/state/zip
|
|
303
|
+
if patient_data.get('subscriber_city'):
|
|
304
|
+
n4_segment = "N4*{}*{}*{}~".format(
|
|
305
|
+
patient_data.get('subscriber_city', ''),
|
|
306
|
+
patient_data.get('subscriber_state', ''),
|
|
307
|
+
patient_data.get('subscriber_zip', '')
|
|
308
|
+
)
|
|
309
|
+
segments.append(n4_segment)
|
|
310
|
+
|
|
311
|
+
# Optional DMG segment for date of birth/gender
|
|
312
|
+
if patient_data.get('subscriber_dob'):
|
|
313
|
+
convert_date = get_convert_date_format()
|
|
314
|
+
dmg_segment = "DMG*D8*{}*{}~".format(
|
|
315
|
+
convert_date(patient_data.get('subscriber_dob')),
|
|
316
|
+
patient_data.get('subscriber_gender', '')
|
|
317
|
+
)
|
|
318
|
+
segments.append(dmg_segment)
|
|
319
|
+
|
|
320
|
+
return segments
|
|
321
|
+
|
|
322
|
+
def create_pwk_attachment_segment(patient_data, config):
|
|
323
|
+
"""
|
|
324
|
+
Creates PWK segment for attachment references (non-electronic EOB handling).
|
|
325
|
+
|
|
326
|
+
Example: PWK*EB*FX*123456~
|
|
327
|
+
|
|
328
|
+
Parameters:
|
|
329
|
+
- patient_data: Dictionary containing attachment information
|
|
330
|
+
- config: Configuration settings
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
- PWK segment string or None if not required
|
|
334
|
+
"""
|
|
335
|
+
# Check if attachment is required
|
|
336
|
+
if patient_data.get('requires_attachment', False):
|
|
337
|
+
report_type = patient_data.get('attachment_report_type', 'EB') # EB = Explanation of Benefits
|
|
338
|
+
transmission_code = patient_data.get('attachment_transmission_code', 'FX') # FX = Fax
|
|
339
|
+
control_number = patient_data.get('attachment_control_number', '')
|
|
340
|
+
|
|
341
|
+
if control_number:
|
|
342
|
+
pwk_segment = "PWK*{}*{}*{}~".format(report_type, transmission_code, control_number)
|
|
343
|
+
return pwk_segment
|
|
344
|
+
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
def validate_cob_claim_integrity(patient_data, config):
|
|
348
|
+
"""
|
|
349
|
+
Validates COB claim integrity and compliance.
|
|
350
|
+
|
|
351
|
+
Validation checks:
|
|
352
|
+
- SBR09 required if SBR01 = "S"
|
|
353
|
+
- NM1*PR in 2330B must match Medicare Payer ID when applicable
|
|
354
|
+
- CLM segment must reflect consistent claim frequency and COB type
|
|
355
|
+
- Total paid validation: AMT*D in Loop 2320 matches sum of SVD02 values
|
|
356
|
+
|
|
357
|
+
Parameters:
|
|
358
|
+
- patient_data: Dictionary containing claim data
|
|
359
|
+
- config: Configuration settings
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
- Tuple of (is_valid, error_messages)
|
|
363
|
+
"""
|
|
364
|
+
errors = []
|
|
365
|
+
|
|
366
|
+
# Validate SBR09 for secondary claims
|
|
367
|
+
if patient_data.get('claim_type') == 'secondary':
|
|
368
|
+
insurance_type = determine_medicare_payer_type(patient_data, config)
|
|
369
|
+
if not insurance_type:
|
|
370
|
+
errors.append("SBR09 required when SBR01 = S (secondary claim)")
|
|
371
|
+
|
|
372
|
+
# Validate Medicare Payer ID
|
|
373
|
+
if patient_data.get('prior_payer_id') == '00850': # Medicare
|
|
374
|
+
if patient_data.get('prior_payer_name') != 'MEDICARE':
|
|
375
|
+
errors.append("NM1*PR must match Medicare Payer ID 00850")
|
|
376
|
+
|
|
377
|
+
# Validate claim frequency for COB claims
|
|
378
|
+
if patient_data.get('claim_type') == 'secondary':
|
|
379
|
+
claim_frequency = patient_data.get('claim_frequency', '1')
|
|
380
|
+
if claim_frequency != '1':
|
|
381
|
+
errors.append("CLM05-3 must be '1' for original COB claims")
|
|
382
|
+
|
|
383
|
+
# Validate total paid amount
|
|
384
|
+
amt_d_total = float(patient_data.get('primary_paid_amount', '0.00'))
|
|
385
|
+
svd_amounts = [float(s.get('paid_amount', '0.00')) for s in patient_data.get('service_adjudications', [])]
|
|
386
|
+
if abs(amt_d_total - sum(svd_amounts)) > 0.01: # Allow for rounding differences
|
|
387
|
+
errors.append("AMT*D total must match sum of SVD02 amounts")
|
|
388
|
+
|
|
389
|
+
return len(errors) == 0, errors
|
|
390
|
+
|
|
391
|
+
def extract_835_adjudication_data(patient_data, config):
|
|
392
|
+
"""
|
|
393
|
+
Extracts and validates 835-derived adjudication data for COB embedding.
|
|
394
|
+
|
|
395
|
+
Extracts:
|
|
396
|
+
- CLP02 → AMT*D (2320)
|
|
397
|
+
- CAS segments → CAS (2320 and/or 2430)
|
|
398
|
+
- SVC03 → SVD02 (2430)
|
|
399
|
+
- DTP segments → DTP*573 (2430)
|
|
400
|
+
|
|
401
|
+
Parameters:
|
|
402
|
+
- patient_data: Dictionary containing 835 data
|
|
403
|
+
- config: Configuration settings
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
- Dictionary of extracted adjudication data or None if invalid
|
|
407
|
+
"""
|
|
408
|
+
adjudication_data = {}
|
|
409
|
+
|
|
410
|
+
# Extract total paid amount from CLP02
|
|
411
|
+
adjudication_data['total_paid'] = patient_data.get('clp02_amount', '0.00')
|
|
412
|
+
|
|
413
|
+
# Extract CAS adjustments
|
|
414
|
+
cas_adjustments = patient_data.get('cas_segments', [])
|
|
415
|
+
adjudication_data['cas_adjustments'] = []
|
|
416
|
+
for cas in cas_adjustments:
|
|
417
|
+
adjustment = {
|
|
418
|
+
'group': cas.get('group', 'CO'),
|
|
419
|
+
'reason': cas.get('reason', ''),
|
|
420
|
+
'amount': cas.get('amount', '0.00')
|
|
421
|
+
}
|
|
422
|
+
adjudication_data['cas_adjustments'].append(adjustment)
|
|
423
|
+
|
|
424
|
+
# Extract service line paid amounts from SVC03
|
|
425
|
+
service_adjudications = patient_data.get('svc_segments', [])
|
|
426
|
+
adjudication_data['service_paid_amounts'] = []
|
|
427
|
+
for svc in service_adjudications:
|
|
428
|
+
service = {
|
|
429
|
+
'payer_id': svc.get('payer_id', '00850'),
|
|
430
|
+
'paid_amount': svc.get('paid_amount', '0.00'),
|
|
431
|
+
'revenue_code': svc.get('revenue_code', ''),
|
|
432
|
+
'units': svc.get('units', '1'),
|
|
433
|
+
'adjudication_date': svc.get('adjudication_date', ''),
|
|
434
|
+
'adjustments': svc.get('adjustments', [])
|
|
435
|
+
}
|
|
436
|
+
adjudication_data['service_paid_amounts'].append(service)
|
|
437
|
+
|
|
438
|
+
# Validate 835 structure
|
|
439
|
+
if not validate_835_structure(patient_data):
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
return adjudication_data
|
|
443
|
+
|
|
444
|
+
def validate_835_structure(patient_data):
|
|
445
|
+
"""
|
|
446
|
+
Validates that 835 data has required structure for COB processing.
|
|
447
|
+
|
|
448
|
+
Parameters:
|
|
449
|
+
- patient_data: Dictionary containing 835 data
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
- Boolean indicating if 835 structure is valid
|
|
453
|
+
"""
|
|
454
|
+
required_fields = ['clp02_amount', 'cas_segments', 'svc_segments']
|
|
455
|
+
|
|
456
|
+
for field in required_fields:
|
|
457
|
+
if field not in patient_data:
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
return True
|
|
461
|
+
|
|
462
|
+
def determine_medicare_payer_type(patient_data, config):
|
|
463
|
+
"""
|
|
464
|
+
Determines Medicare payer type for proper SBR09 assignment.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
- "MB" for Medicare Part B
|
|
468
|
+
- "MA" for Medicare Advantage
|
|
469
|
+
- None if not Medicare
|
|
470
|
+
"""
|
|
471
|
+
payer_id = patient_data.get('payer_id', '')
|
|
472
|
+
|
|
473
|
+
if payer_id == "00850": # Medicare
|
|
474
|
+
# Check if this is Medicare Advantage
|
|
475
|
+
if patient_data.get('medicare_advantage', False):
|
|
476
|
+
return "MA"
|
|
477
|
+
else:
|
|
478
|
+
return "MB"
|
|
479
|
+
|
|
480
|
+
return None
|
|
481
|
+
|
|
482
|
+
def create_enhanced_sbr_segment(patient_data, config, crosswalk):
|
|
483
|
+
"""
|
|
484
|
+
Enhanced SBR segment creation supporting secondary indicators and Medicare payer types.
|
|
485
|
+
|
|
486
|
+
Enhancement:
|
|
487
|
+
- Support secondary indicator (SBR01 = "S")
|
|
488
|
+
- Proper payer type in SBR09 ("MB" for Medicare Part B, "MA" for Medicare Advantage)
|
|
489
|
+
|
|
490
|
+
Parameters:
|
|
491
|
+
- patient_data: Dictionary containing patient and payer information
|
|
492
|
+
- config: Configuration settings
|
|
493
|
+
- crosswalk: Crosswalk data for payer mapping
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
- Enhanced SBR segment string
|
|
497
|
+
"""
|
|
498
|
+
# Determine responsibility code
|
|
499
|
+
responsibility_code = "S" if patient_data.get('claim_type') == 'secondary' else "P"
|
|
500
|
+
|
|
501
|
+
# Determine Medicare payer type
|
|
502
|
+
medicare_type = determine_medicare_payer_type(patient_data, config)
|
|
503
|
+
if medicare_type:
|
|
504
|
+
insurance_type = medicare_type
|
|
505
|
+
else:
|
|
506
|
+
insurance_type = patient_data.get('insurance_type', '18')
|
|
507
|
+
|
|
508
|
+
sbr_segment = "SBR*{}*18*******{}~".format(responsibility_code, insurance_type)
|
|
509
|
+
|
|
510
|
+
return sbr_segment
|
|
511
|
+
|
|
512
|
+
def create_enhanced_clm_segment(patient_data, config, crosswalk):
|
|
513
|
+
"""
|
|
514
|
+
Enhanced CLM segment creation ensuring proper claim frequency for COB.
|
|
515
|
+
|
|
516
|
+
Enhancement:
|
|
517
|
+
- Ensure CLM05-3 is explicitly set to "1" (original) for secondary claims
|
|
518
|
+
- Unless claim replacement logic applies
|
|
519
|
+
|
|
520
|
+
Parameters:
|
|
521
|
+
- patient_data: Dictionary containing claim information
|
|
522
|
+
- config: Configuration settings
|
|
523
|
+
- crosswalk: Crosswalk data for claim mapping
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
- Enhanced CLM segment string
|
|
527
|
+
"""
|
|
528
|
+
# Get claim details
|
|
529
|
+
claim_number = patient_data.get('CHART', '')
|
|
530
|
+
amount = patient_data.get('AMOUNT', '0.00')
|
|
531
|
+
type_of_service = patient_data.get('TOS', '')
|
|
532
|
+
|
|
533
|
+
# Determine claim frequency
|
|
534
|
+
if patient_data.get('claim_type') == 'secondary':
|
|
535
|
+
claim_frequency = "1" # Original for secondary claims
|
|
536
|
+
else:
|
|
537
|
+
claim_frequency = patient_data.get('claim_frequency', '1')
|
|
538
|
+
|
|
539
|
+
clm_segment = "CLM*{}*{}***{}:B:{}*Y*A*Y*Y~".format(
|
|
540
|
+
claim_number, amount, type_of_service, claim_frequency)
|
|
541
|
+
|
|
542
|
+
return clm_segment
|
|
543
|
+
|
|
544
|
+
def validate_cob_multi_payer_handling(patient_data, config):
|
|
545
|
+
"""
|
|
546
|
+
Validates COB multi-payer handling configuration.
|
|
547
|
+
|
|
548
|
+
Mode: single_payer_only (Options: single_payer_only, multi_payer_supported)
|
|
549
|
+
Rejection action: raise configuration error if >1 COB payer detected
|
|
550
|
+
|
|
551
|
+
Parameters:
|
|
552
|
+
- patient_data: Dictionary containing COB payer information
|
|
553
|
+
- config: Configuration settings
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
- Tuple of (is_valid, error_messages)
|
|
557
|
+
"""
|
|
558
|
+
errors = []
|
|
559
|
+
|
|
560
|
+
# Count COB payers
|
|
561
|
+
cob_payers = len(patient_data.get('cob_payers', []))
|
|
562
|
+
cob_mode = get_cob_configuration(config).get('cob_mode', 'single_payer_only')
|
|
563
|
+
|
|
564
|
+
if cob_payers > 1 and cob_mode == 'single_payer_only':
|
|
565
|
+
errors.append("Multiple COB payers detected but single_payer_only mode enabled")
|
|
566
|
+
|
|
567
|
+
return len(errors) == 0, errors
|
|
568
|
+
|
|
569
|
+
def log_cob_validation_results(validation_results, config):
|
|
570
|
+
"""
|
|
571
|
+
Logs COB validation results for audit and debugging.
|
|
572
|
+
|
|
573
|
+
Parameters:
|
|
574
|
+
- validation_results: Dictionary containing validation results
|
|
575
|
+
- config: Configuration settings
|
|
576
|
+
"""
|
|
577
|
+
for validation_type, result in validation_results.items():
|
|
578
|
+
if result['is_valid']:
|
|
579
|
+
MediLink_ConfigLoader.log("COB validation passed: {}".format(validation_type), config, level="INFO")
|
|
580
|
+
else:
|
|
581
|
+
MediLink_ConfigLoader.log("COB validation failed: {}".format(validation_type), config, level="ERROR")
|
|
582
|
+
for error in result['errors']:
|
|
583
|
+
MediLink_ConfigLoader.log(" - {}".format(error), config, level="ERROR")
|
|
584
|
+
|
|
585
|
+
# Configuration enhancement functions
|
|
586
|
+
|
|
587
|
+
def get_cob_configuration(config):
|
|
588
|
+
"""
|
|
589
|
+
Retrieves COB-specific configuration settings.
|
|
590
|
+
|
|
591
|
+
Parameters:
|
|
592
|
+
- config: Main configuration dictionary
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
- COB configuration dictionary
|
|
596
|
+
"""
|
|
597
|
+
cob_config = config.get('MediLink_Config', {}).get('cob_settings', {})
|
|
598
|
+
return cob_config
|
|
599
|
+
|
|
600
|
+
def validate_cob_configuration(config):
|
|
601
|
+
"""
|
|
602
|
+
Validates COB configuration completeness and correctness.
|
|
603
|
+
|
|
604
|
+
Parameters:
|
|
605
|
+
- config: Configuration settings
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
- Tuple of (is_valid, error_messages)
|
|
609
|
+
"""
|
|
610
|
+
errors = []
|
|
611
|
+
|
|
612
|
+
cob_config = get_cob_configuration(config)
|
|
613
|
+
required_fields = ['medicare_payer_ids', 'cob_mode', 'validation_level']
|
|
614
|
+
|
|
615
|
+
for field in required_fields:
|
|
616
|
+
if field not in cob_config:
|
|
617
|
+
errors.append("Missing required COB configuration field: {}".format(field))
|
|
618
|
+
|
|
619
|
+
return len(errors) == 0, errors
|
|
620
|
+
|
|
621
|
+
# Insurance type code enhancement
|
|
622
|
+
|
|
623
|
+
def get_enhanced_insurance_options(config):
|
|
624
|
+
"""
|
|
625
|
+
Retrieves enhanced insurance options including Medicare-specific codes.
|
|
626
|
+
|
|
627
|
+
Parameters:
|
|
628
|
+
- config: Configuration settings
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
- Enhanced insurance options dictionary
|
|
632
|
+
"""
|
|
633
|
+
base_options = config.get('MediLink_Config', {}).get('insurance_options', {})
|
|
634
|
+
medicare_options = {
|
|
635
|
+
'MB': 'Medicare Part B',
|
|
636
|
+
'MA': 'Medicare Advantage',
|
|
637
|
+
'MC': 'Medicare Part C'
|
|
638
|
+
}
|
|
639
|
+
enhanced_options = {**base_options, **medicare_options}
|
|
640
|
+
return enhanced_options
|
|
641
|
+
|
|
642
|
+
# Main COB processing function
|
|
643
|
+
|
|
644
|
+
def process_cob_claim(patient_data, config, crosswalk, client):
|
|
645
|
+
"""
|
|
646
|
+
Main function for processing COB claims with enhanced 837P generation.
|
|
647
|
+
|
|
648
|
+
This function orchestrates the creation of all COB-related segments
|
|
649
|
+
and validates the complete claim for compliance.
|
|
650
|
+
|
|
651
|
+
Parameters:
|
|
652
|
+
- patient_data: Dictionary containing patient and COB information
|
|
653
|
+
- config: Configuration settings
|
|
654
|
+
- crosswalk: Crosswalk data for mapping
|
|
655
|
+
- client: API client for external data retrieval
|
|
656
|
+
|
|
657
|
+
Returns:
|
|
658
|
+
- List of all 837P segments including COB enhancements
|
|
659
|
+
"""
|
|
660
|
+
segments = []
|
|
661
|
+
|
|
662
|
+
# 1. Validate COB configuration
|
|
663
|
+
is_valid, errors = validate_cob_configuration(config)
|
|
664
|
+
if not is_valid:
|
|
665
|
+
raise ValueError("Invalid COB configuration: {}".format(errors))
|
|
666
|
+
|
|
667
|
+
# 2. Extract 835 adjudication data if available
|
|
668
|
+
adjudication_data = extract_835_adjudication_data(patient_data, config)
|
|
669
|
+
if adjudication_data:
|
|
670
|
+
# Merge 835 data into patient_data
|
|
671
|
+
patient_data.update(adjudication_data)
|
|
672
|
+
|
|
673
|
+
# 3. Create enhanced SBR segment
|
|
674
|
+
enhanced_sbr = create_enhanced_sbr_segment(patient_data, config, crosswalk)
|
|
675
|
+
if enhanced_sbr:
|
|
676
|
+
segments.append(enhanced_sbr)
|
|
677
|
+
|
|
678
|
+
# 4. Create 2320 other subscriber segments if COB
|
|
679
|
+
if patient_data.get('claim_type') == 'secondary':
|
|
680
|
+
segments.extend(create_2320_other_subscriber_segments(patient_data, config, crosswalk))
|
|
681
|
+
|
|
682
|
+
# 5. Create 2330B prior payer segments
|
|
683
|
+
segments.extend(create_2330B_prior_payer_segments(patient_data, config, crosswalk))
|
|
684
|
+
|
|
685
|
+
# 6. Create 2330C other subscriber segments if patient != subscriber
|
|
686
|
+
if not patient_data.get('patient_is_subscriber', True):
|
|
687
|
+
segments.extend(create_2330C_other_subscriber_name_segments(patient_data, config, crosswalk))
|
|
688
|
+
|
|
689
|
+
# 7. Create enhanced CLM segment
|
|
690
|
+
enhanced_clm = create_enhanced_clm_segment(patient_data, config, crosswalk)
|
|
691
|
+
if enhanced_clm:
|
|
692
|
+
segments.append(enhanced_clm)
|
|
693
|
+
|
|
694
|
+
# 8. Create 2430 service line COB segments if service-level adjudication
|
|
695
|
+
if patient_data.get('service_adjudications'):
|
|
696
|
+
segments.extend(create_2430_service_line_cob_segments(patient_data, config, crosswalk))
|
|
697
|
+
|
|
698
|
+
# 9. Create PWK attachment segment if required
|
|
699
|
+
pwk_segment = create_pwk_attachment_segment(patient_data, config)
|
|
700
|
+
if pwk_segment:
|
|
701
|
+
segments.append(pwk_segment)
|
|
702
|
+
|
|
703
|
+
# 10. Validate final claim integrity
|
|
704
|
+
is_valid, errors = validate_cob_claim_integrity(patient_data, config)
|
|
705
|
+
if not is_valid:
|
|
706
|
+
raise ValueError("COB claim validation failed: {}".format(errors))
|
|
707
|
+
|
|
708
|
+
# 11. Log validation results
|
|
709
|
+
log_cob_validation_results({'integrity': {'is_valid': is_valid, 'errors': errors}}, config)
|
|
710
|
+
|
|
711
|
+
return segments
|
|
712
|
+
|
|
713
|
+
# Configuration Documentation and Examples
|
|
714
|
+
|
|
715
|
+
"""
|
|
716
|
+
COB CONFIGURATION STRUCTURE
|
|
717
|
+
|
|
718
|
+
The COB library requires specific configuration settings to be added to the main config.json file.
|
|
719
|
+
Below is the recommended structure for COB configuration:
|
|
720
|
+
|
|
721
|
+
{
|
|
722
|
+
"MediLink_Config": {
|
|
723
|
+
"cob_settings": {
|
|
724
|
+
"medicare_payer_ids": ["00850"],
|
|
725
|
+
"cob_mode": "single_payer_only",
|
|
726
|
+
"validation_level": 3,
|
|
727
|
+
"medicare_advantage_identifiers": ["MA", "MC"],
|
|
728
|
+
"default_medicare_type": "MB",
|
|
729
|
+
"require_835_validation": true,
|
|
730
|
+
"attachment_handling": {
|
|
731
|
+
"enable_pwk_segments": true,
|
|
732
|
+
"default_report_type": "EB",
|
|
733
|
+
"default_transmission_code": "FX"
|
|
734
|
+
},
|
|
735
|
+
"service_level_adjudication": {
|
|
736
|
+
"enable_2430_loop": true,
|
|
737
|
+
"require_adjudication_date": true,
|
|
738
|
+
"validate_paid_amounts": true
|
|
739
|
+
},
|
|
740
|
+
"validation_rules": {
|
|
741
|
+
"require_sbr09_for_secondary": true,
|
|
742
|
+
"validate_medicare_payer_id": true,
|
|
743
|
+
"require_claim_frequency_1": true,
|
|
744
|
+
"validate_total_paid_amount": true
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
"insurance_options": {
|
|
748
|
+
"MB": "Medicare Part B",
|
|
749
|
+
"MA": "Medicare Advantage",
|
|
750
|
+
"MC": "Medicare Part C",
|
|
751
|
+
"18": "Medicare",
|
|
752
|
+
"12": "Medicaid",
|
|
753
|
+
"16": "Self Pay"
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
COB INTEGRATION GUIDE
|
|
759
|
+
|
|
760
|
+
1. DATA STRUCTURE REQUIREMENTS
|
|
761
|
+
|
|
762
|
+
Patient data for COB claims should include:
|
|
763
|
+
- claim_type: "primary" or "secondary"
|
|
764
|
+
- payer_id: Payer identifier (e.g., "00850" for Medicare)
|
|
765
|
+
- medicare_advantage: Boolean indicating if Medicare Advantage
|
|
766
|
+
- primary_paid_amount: Amount paid by primary payer
|
|
767
|
+
- cas_adjustments: List of adjustment objects
|
|
768
|
+
- service_adjudications: List of service-level adjudication data
|
|
769
|
+
- prior_payer_name: Name of prior payer
|
|
770
|
+
- prior_payer_id: ID of prior payer
|
|
771
|
+
- patient_is_subscriber: Boolean indicating if patient is subscriber
|
|
772
|
+
- requires_attachment: Boolean indicating if attachment required
|
|
773
|
+
|
|
774
|
+
2. 835 DATA INTEGRATION
|
|
775
|
+
|
|
776
|
+
For 835-derived adjudication data, include:
|
|
777
|
+
- clp02_amount: Total amount paid by primary
|
|
778
|
+
- cas_segments: List of CAS adjustment segments
|
|
779
|
+
- svc_segments: List of service line segments with:
|
|
780
|
+
- payer_id: Payer identifier
|
|
781
|
+
- paid_amount: Amount paid for service
|
|
782
|
+
- revenue_code: Revenue code
|
|
783
|
+
- units: Number of units
|
|
784
|
+
- adjudication_date: Date of adjudication
|
|
785
|
+
- adjustments: List of line-level adjustments
|
|
786
|
+
|
|
787
|
+
3. VALIDATION REQUIREMENTS
|
|
788
|
+
|
|
789
|
+
The library performs comprehensive validation:
|
|
790
|
+
- SBR09 required for secondary claims
|
|
791
|
+
- Medicare payer ID validation
|
|
792
|
+
- Claim frequency validation
|
|
793
|
+
- Total paid amount validation
|
|
794
|
+
- 835 data structure validation
|
|
795
|
+
|
|
796
|
+
4. INTEGRATION POINTS
|
|
797
|
+
|
|
798
|
+
Key integration points in the main encoder:
|
|
799
|
+
- Enhanced SBR segment creation
|
|
800
|
+
- COB loop processing (2320, 2330B, 2330C, 2430)
|
|
801
|
+
- Enhanced CLM segment creation
|
|
802
|
+
- PWK attachment segment creation
|
|
803
|
+
|
|
804
|
+
5. ERROR HANDLING
|
|
805
|
+
|
|
806
|
+
The library provides detailed error messages for:
|
|
807
|
+
- Configuration validation failures
|
|
808
|
+
- Claim integrity violations
|
|
809
|
+
- 835 data structure issues
|
|
810
|
+
- Medicare payer type determination failures
|
|
811
|
+
|
|
812
|
+
6. LOGGING
|
|
813
|
+
|
|
814
|
+
Comprehensive logging is provided for:
|
|
815
|
+
- COB validation results
|
|
816
|
+
- Configuration validation
|
|
817
|
+
- Claim processing steps
|
|
818
|
+
- Error conditions
|
|
819
|
+
|
|
820
|
+
7. FUTURE ENHANCEMENTS
|
|
821
|
+
|
|
822
|
+
Planned enhancements include:
|
|
823
|
+
- Multi-payer COB support
|
|
824
|
+
- Advanced 835 data extraction
|
|
825
|
+
- Enhanced validation rules
|
|
826
|
+
- Attachment workflow integration
|
|
827
|
+
- Real-time payer verification
|
|
828
|
+
|
|
829
|
+
USAGE EXAMPLE
|
|
830
|
+
|
|
831
|
+
# Basic COB claim processing
|
|
832
|
+
patient_data = {
|
|
833
|
+
'claim_type': 'secondary',
|
|
834
|
+
'payer_id': '00850',
|
|
835
|
+
'medicare_advantage': False,
|
|
836
|
+
'primary_paid_amount': '150.00',
|
|
837
|
+
'cas_adjustments': [
|
|
838
|
+
{'group': 'CO', 'reason': '45', 'amount': '25.00'}
|
|
839
|
+
],
|
|
840
|
+
'service_adjudications': [
|
|
841
|
+
{
|
|
842
|
+
'payer_id': '00850',
|
|
843
|
+
'paid_amount': '125.00',
|
|
844
|
+
'revenue_code': '0001',
|
|
845
|
+
'units': '1',
|
|
846
|
+
'adjudication_date': '01-15-2024',
|
|
847
|
+
'adjustments': []
|
|
848
|
+
}
|
|
849
|
+
]
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
# Process COB claim
|
|
853
|
+
cob_segments = process_cob_claim(patient_data, config, crosswalk, client)
|
|
854
|
+
|
|
855
|
+
# Validate configuration
|
|
856
|
+
is_valid, errors = validate_cob_configuration(config)
|
|
857
|
+
if not is_valid:
|
|
858
|
+
print("Configuration errors:", errors)
|
|
859
|
+
|
|
860
|
+
# Get enhanced insurance options
|
|
861
|
+
insurance_options = get_enhanced_insurance_options(config)
|
|
862
|
+
"""
|