Case Study 2: MedClaim's Claim Status Lookup Screen
Background
MedClaim Health Services' call center receives approximately 3,000 calls per day. The number one question: "What is the status of my claim?" Before CICS, customer service representatives accessed claim information through a batch-generated report printed nightly. If a claim was submitted after the report ran, the representative could only say "It is being processed."
James Okafor's team was tasked with building a real-time claim status inquiry (transaction CINQ) that could display the current status of any claim, including those submitted that same day.
System Design
The Screen Layout
MedClaim's claim status screen needed to display significantly more information than GlobalBank's simple account inquiry:
Row 1: MEDCLAIM CLAIM STATUS INQUIRY Date Time
Row 3: Claim Number: [______________]
Row 5: Status: APPROVED Adjud Date: 2024-11-01
Row 7: Member: John Smith Member ID: M00123456
Row 9: Provider: Dr. Jane Wilson Network: In-Network
Row 11: Service Code: 99213 Service Date: 2024-10-15
Row 13: Amount Billed: $ 150.00
Row 14: Amount Allowed: $ 125.00
Row 15: Member Copay: $ 25.00
Row 16: Amount Paid: $ 100.00
Row 18: Denial Reason: N/A
Row 21: Enter=Lookup PF3=Exit PF5=Print PF7=History
Row 23: Transaction: CINQ
The Multi-Table Challenge
Unlike GlobalBank's single-table inquiry, MedClaim's claim status requires data from four tables:
- CLAIM — claim number, status, amounts, dates
- MEMBER — member name, ID
- PROVIDER — provider name, network status
- PAYMENT — payment amounts, copay, adjustments
Sarah Kim insisted that all data display in a single screen, even though it required a four-table JOIN:
SELECT C.CLAIM_NUMBER, C.CLAIM_STATUS,
C.CLAIM_AMOUNT, C.SERVICE_CODE,
C.SERVICE_DATE, C.SUBMIT_DATE,
C.ADJUD_DATE, C.ADJUD_REASON,
M.MEMBER_NAME, M.MEMBER_ID,
P.PROVIDER_NAME, P.NETWORK_STATUS,
PY.AMOUNT_ALLOWED, PY.MEMBER_COPAY,
PY.AMOUNT_PAID
FROM CLAIM C
JOIN MEMBER M ON C.MEMBER_ID = M.MEMBER_ID
JOIN PROVIDER P ON C.PROVIDER_ID = P.PROVIDER_ID
LEFT JOIN PAYMENT PY ON C.CLAIM_NUMBER = PY.CLAIM_NUMBER
WHERE C.CLAIM_NUMBER = :WS-CLAIM-NUM
The LEFT JOIN on PAYMENT was critical — not all claims have payment records (pending and denied claims do not). This required indicator variables for all PAYMENT columns.
The PF7 Challenge: Claim History
Sarah wanted PF7 to display the claim's processing history — a timeline of every status change. This required a second screen, which meant the transaction needed multi-screen state management via the COMMAREA:
01 WS-COMMAREA.
05 WS-CA-STATE PIC X(1).
88 WS-CA-INQUIRY VALUE 'I'.
88 WS-CA-HISTORY VALUE 'H'.
05 WS-CA-CLAIM-NUM PIC X(15).
05 WS-CA-HIST-POS PIC S9(4) COMP.
When PF7 is pressed: 1. Save the current claim number and state in COMMAREA 2. Query the CLAIM_HISTORY table for status changes 3. SEND a different map (CLMHIST instead of CLMSTAT) 4. On the history screen, PF3 returns to the inquiry screen using the saved claim number
Technical Challenges
Challenge 1: Null Handling for Pending Claims
Pending claims have NULL values for ADJUD_DATE, ADJUD_REASON, AMOUNT_ALLOWED, MEMBER_COPAY, and AMOUNT_PAID. Without indicator variables, these NULLs caused SQLCODE -305 abends. The fix required indicator variables for every nullable column:
01 WS-INDICATORS.
05 WS-IND-ADJUD-DATE PIC S9(4) COMP.
05 WS-IND-ADJUD-REASON PIC S9(4) COMP.
05 WS-IND-AMT-ALLOWED PIC S9(4) COMP.
05 WS-IND-COPAY PIC S9(4) COMP.
05 WS-IND-AMT-PAID PIC S9(4) COMP.
For each field, the display logic checked the indicator:
IF WS-IND-AMT-PAID = -1
MOVE 'Pending' TO AMTPAIDO
ELSE
MOVE WS-AMT-PAID TO WS-FMT-AMT
MOVE WS-FMT-AMT TO AMTPAIDO
END-IF
Challenge 2: Performance Under Load
Initial testing showed response times of 0.8 seconds — within the 1-second target. But during load testing with 500 concurrent users, response time spiked to 3.2 seconds. Tomás Rivera identified the cause: the four-table JOIN was causing excessive lock contention because the default isolation level (CS) held locks during the fetch.
Solution: Since this is a read-only inquiry, the query was changed to WITH UR:
SELECT ... FROM CLAIM C ... WHERE C.CLAIM_NUMBER = ? WITH UR
Response time under load dropped to 0.3 seconds.
Challenge 3: The Stale Screen Problem
Representatives sometimes left the claim status screen displayed while on a long call. When they looked at it 20 minutes later, the data was stale — the claim might have been adjudicated during that time. PF5 ("Refresh") was added to re-query and redisplay the current data without the representative needing to retype the claim number:
WHEN DFHPF5
IF WS-CA-CLAIM-NUM NOT = SPACES
PERFORM 2000-LOOKUP-CLAIM
ELSE
MOVE 'No claim to refresh' TO MSGO
PERFORM 1500-SEND-DATAONLY
END-IF
The Modernization Path
Two years after the CICS screen was deployed, MedClaim launched a member web portal. Instead of building new query logic, the team:
- Extracted the claim status logic from CINQ into a separate program (CLMLKUP) that accepted a claim number via COMMAREA and returned the data
- The CICS screen program (CINQ) used LINK to call CLMLKUP
- A new CICS Liberty servlet also used LINK to call the same CLMLKUP
- The web portal called the servlet via REST
The core COBOL logic — the SQL query, null handling, and data formatting — was written once and served both channels.
Results
| Metric | Before | After |
|---|---|---|
| Claim status calls | 1,200/day | 400/day (67% reduction) |
| Average response time | Next-day report | 0.3 seconds |
| Call handle time for status inquiries | 3.5 minutes | 1.2 minutes |
| Web portal self-service lookups | 0/day | 2,500/day |
| COBOL programs serving web portal | 0 | 1 (shared with CICS) |
Discussion Questions
- The LEFT JOIN on PAYMENT was necessary because not all claims have payment records. What would happen if an INNER JOIN was used instead? How would this affect the user experience?
- Why was extracting the lookup logic into a separate program (CLMLKUP) a better architecture than having the servlet directly access DB2? What does this have to do with the Modernization Spectrum theme?
- The COMMAREA stores claim number and state but not the full claim data. Why not store the entire claim record in the COMMAREA to avoid re-querying when navigating between screens?
- How would you handle a scenario where a claim number entered by a representative matches multiple claims (e.g., if the same provider submitted duplicate claims)? What screen design changes would be needed?