Case Study 2: Adding Fraud Detection to the Banking System

Background

Community Bank has experienced a steady increase in fraudulent activity over the past two years. The losses are modest by large-bank standards -- $340,000 in the most recent fiscal year -- but the trend is accelerating. The compliance officer has identified three categories of fraud that the current capstone application does not detect:

Velocity fraud: Rapid sequences of transactions that exploit the delay between authorization and posting. A common pattern is a compromised debit card used at multiple ATMs within minutes, withdrawing the daily limit at each machine before the batch cycle posts the transactions and reduces the available balance.

Structuring: Deliberate splitting of large cash transactions into amounts just below the $10,000 Currency Transaction Report threshold. While the capstone already generates CTRs for transactions at or above $10,000, it does not aggregate multiple transactions by the same customer within a single day to detect structuring.

Account takeover: Unauthorized access to customer accounts, often following a social engineering attack. The indicators include address changes followed by large withdrawals, password resets followed by wire transfers, and transactions from geographic locations inconsistent with the customer's profile.

The bank's compliance committee has mandated the implementation of a fraud detection module that integrates with the existing capstone application. The module must operate in two modes: real-time screening during CICS transaction processing (to block or flag suspicious activity before it completes) and batch analysis during the nightly cycle (to detect patterns that span an entire business day).


Phase 1: Fraud Rule Definition

The Rule Engine Data Architecture

The team designs a rule-based detection system rather than a hardcoded approach, allowing the compliance team to add, modify, and disable rules without program changes. The rule definitions are stored in a DB2 table:

           EXEC SQL
               CREATE TABLE FRAUD_RULES
               ( RULE_ID        CHAR(6)      NOT NULL,
                 RULE_NAME      VARCHAR(50)  NOT NULL,
                 RULE_TYPE      CHAR(2)      NOT NULL,
                 RULE_STATUS    CHAR(1)      NOT NULL
                   DEFAULT 'A',
                 THRESHOLD_AMT  DECIMAL(11,2),
                 THRESHOLD_COUNT INTEGER,
                 TIME_WINDOW_MIN INTEGER,
                 ACCT_TYPES     CHAR(5),
                 CHANNEL_TYPES  CHAR(14),
                 RISK_SCORE     SMALLINT     NOT NULL,
                 ACTION_CODE    CHAR(1)      NOT NULL,
                 EFFECTIVE_DATE DATE         NOT NULL,
                 EXPIRY_DATE    DATE,
                 PRIMARY KEY (RULE_ID) )
           END-EXEC

The COBOL copybook mirrors this structure for use in both CICS and batch programs:

      *================================================================*
      * CAPSTONE-FRAUD-RULE.CPY                                        *
      * Fraud Detection Rule Definition Record                         *
      *================================================================*
       01  FRAUD-RULE-RECORD.
           05  FR-RULE-ID             PIC X(06).
           05  FR-RULE-NAME           PIC X(50).
           05  FR-RULE-TYPE           PIC X(02).
               88  FR-TYPE-VELOCITY   VALUE 'VL'.
               88  FR-TYPE-AMOUNT     VALUE 'AM'.
               88  FR-TYPE-STRUCTURE  VALUE 'ST'.
               88  FR-TYPE-GEO        VALUE 'GE'.
               88  FR-TYPE-BEHAVIOR   VALUE 'BH'.
               88  FR-TYPE-TAKEOVER   VALUE 'TO'.
           05  FR-RULE-STATUS         PIC X(01).
               88  FR-ACTIVE          VALUE 'A'.
               88  FR-INACTIVE        VALUE 'I'.
               88  FR-TEST-MODE       VALUE 'T'.
           05  FR-THRESHOLD-AMT       PIC S9(9)V99 COMP-3.
           05  FR-THRESHOLD-COUNT     PIC S9(4) COMP.
           05  FR-TIME-WINDOW-MIN     PIC S9(4) COMP.
           05  FR-ACCT-TYPES          PIC X(05).
           05  FR-CHANNEL-TYPES       PIC X(14).
           05  FR-RISK-SCORE          PIC S9(4) COMP.
           05  FR-ACTION-CODE         PIC X(01).
               88  FR-ACTION-ALLOW    VALUE 'A'.
               88  FR-ACTION-FLAG     VALUE 'F'.
               88  FR-ACTION-HOLD     VALUE 'H'.
               88  FR-ACTION-BLOCK    VALUE 'B'.
           05  FR-EFFECTIVE-DATE      PIC X(10).
           05  FR-EXPIRY-DATE         PIC X(10).

The team defines an initial set of rules:

Rule ID Name Type Threshold Window Action
VL0001 ATM Rapid Withdrawal VL $500 x 3 15 min Block
VL0002 POS Rapid Purchase VL $200 x 5 30 min Flag
AM0001 Large Single Withdrawal AM $5,000 N/A Flag
AM0002 Large Wire Transfer AM $25,000 N/A Hold
ST0001 Cash Structuring - Day ST $10,000 cumulative 1 day Flag
ST0002 Cash Structuring - Week ST $25,000 cumulative 7 days Flag
TO0001 Address Change + Large WD TO $1,000 72 hrs Block
TO0002 New Payee + Large Transfer TO $2,500 24 hrs Hold
GE0001 Multi-State ATM Same Day GE N/A 1 day Flag
BH0001 Dormant Account Activation BH $500 N/A Flag

Loading Rules into CICS Memory

For real-time performance, the fraud rules cannot be looked up in DB2 for every transaction. The team loads the active rules into a CICS shared data table at CICS startup and refreshes them every 30 minutes or on demand:

       01  WS-FRAUD-RULE-TABLE.
           05  WS-FRT-COUNT           PIC 9(03) VALUE ZERO.
           05  WS-FRT-MAX             PIC 9(03) VALUE 100.
           05  WS-FRT-ENTRY OCCURS 100 TIMES.
               10  WS-FRT-RULE-ID    PIC X(06).
               10  WS-FRT-TYPE       PIC X(02).
               10  WS-FRT-THRESH-AMT PIC S9(9)V99 COMP-3.
               10  WS-FRT-THRESH-CNT PIC S9(4) COMP.
               10  WS-FRT-WINDOW     PIC S9(4) COMP.
               10  WS-FRT-ACCT-TYPES PIC X(05).
               10  WS-FRT-CHANNELS   PIC X(14).
               10  WS-FRT-RISK-SCORE PIC S9(4) COMP.
               10  WS-FRT-ACTION     PIC X(01).

       1000-LOAD-FRAUD-RULES.
           MOVE ZERO TO WS-FRT-COUNT

           EXEC SQL
               DECLARE RULE-CSR CURSOR FOR
               SELECT RULE_ID, RULE_TYPE,
                      THRESHOLD_AMT, THRESHOLD_COUNT,
                      TIME_WINDOW_MIN, ACCT_TYPES,
                      CHANNEL_TYPES, RISK_SCORE,
                      ACTION_CODE
               FROM   FRAUD_RULES
               WHERE  RULE_STATUS IN ('A', 'T')
               AND    EFFECTIVE_DATE <= CURRENT DATE
               AND   (EXPIRY_DATE IS NULL
                   OR EXPIRY_DATE >= CURRENT DATE)
               ORDER BY RULE_TYPE, RULE_ID
               FOR FETCH ONLY
           END-EXEC

           EXEC SQL OPEN RULE-CSR END-EXEC

           PERFORM UNTIL SQLCODE NOT = 0
                      OR WS-FRT-COUNT >= WS-FRT-MAX
               EXEC SQL
                   FETCH RULE-CSR
                   INTO :WS-FRT-RULE-ID(WS-FRT-COUNT + 1),
                        :WS-FRT-TYPE(WS-FRT-COUNT + 1),
                        :WS-FRT-THRESH-AMT(WS-FRT-COUNT + 1),
                        :WS-FRT-THRESH-CNT(WS-FRT-COUNT + 1),
                        :WS-FRT-WINDOW(WS-FRT-COUNT + 1),
                        :WS-FRT-ACCT-TYPES(WS-FRT-COUNT + 1),
                        :WS-FRT-CHANNELS(WS-FRT-COUNT + 1),
                        :WS-FRT-RISK-SCORE(WS-FRT-COUNT + 1),
                        :WS-FRT-ACTION(WS-FRT-COUNT + 1)
               END-EXEC
               IF SQLCODE = 0
                   ADD 1 TO WS-FRT-COUNT
               END-IF
           END-PERFORM

           EXEC SQL CLOSE RULE-CSR END-EXEC
           .

Phase 2: Real-Time Transaction Screening

The Screening Interface

Every CICS transaction program in the capstone calls the fraud screening module before committing a transaction. The screening module receives the transaction details and returns an action code:

      *================================================================*
      * CAPFRAUD - Fraud Screening Subprogram                          *
      * Called by all CICS transaction programs before commit           *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CAPFRAUD.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
           COPY CAPSTONE-FRAUD-RULE.

       01  WS-CUMULATIVE-RISK        PIC S9(4) COMP VALUE ZERO.
       01  WS-MAX-ACTION             PIC X(01) VALUE 'A'.
       01  WS-RULE-IDX               PIC 9(03).
       01  WS-MATCH-COUNT            PIC 9(03) VALUE ZERO.

       LINKAGE SECTION.
       01  LS-SCREEN-REQUEST.
           05  LS-ACCT-NUMBER        PIC X(10).
           05  LS-CUST-NUMBER        PIC X(10).
           05  LS-TXN-TYPE           PIC X(02).
           05  LS-TXN-AMOUNT         PIC S9(11)V99 COMP-3.
           05  LS-TXN-CHANNEL        PIC X(02).
           05  LS-TXN-BRANCH         PIC X(04).
           05  LS-ACCT-TYPE          PIC X(01).
       01  LS-SCREEN-RESULT.
           05  LS-ACTION-CODE        PIC X(01).
               88  LS-ALLOW          VALUE 'A'.
               88  LS-FLAG           VALUE 'F'.
               88  LS-HOLD           VALUE 'H'.
               88  LS-BLOCK          VALUE 'B'.
           05  LS-RISK-SCORE         PIC S9(4) COMP.
           05  LS-TRIGGERED-RULES    PIC X(60).
           05  LS-ALERT-MESSAGE      PIC X(80).

       PROCEDURE DIVISION USING LS-SCREEN-REQUEST
                                LS-SCREEN-RESULT.
       0000-MAIN.
           INITIALIZE LS-SCREEN-RESULT
           SET LS-ALLOW TO TRUE
           MOVE ZERO TO WS-CUMULATIVE-RISK
           MOVE 'A' TO WS-MAX-ACTION
           MOVE ZERO TO WS-MATCH-COUNT

           PERFORM 1000-CHECK-VELOCITY-RULES
           PERFORM 2000-CHECK-AMOUNT-RULES
           PERFORM 3000-CHECK-BEHAVIOR-RULES

           MOVE WS-CUMULATIVE-RISK TO LS-RISK-SCORE
           MOVE WS-MAX-ACTION TO LS-ACTION-CODE
           GOBACK.

Velocity Detection

The velocity check examines recent transaction history to detect rapid sequences. The team maintains a Transaction Velocity Table (TVT) in a CICS Temporary Storage queue, keyed by account number, that stores the last 20 transactions for rapid lookup:

       1000-CHECK-VELOCITY-RULES.
           PERFORM VARYING WS-RULE-IDX FROM 1 BY 1
               UNTIL WS-RULE-IDX > WS-FRT-COUNT
               IF WS-FRT-TYPE(WS-RULE-IDX) = 'VL'
                   PERFORM 1100-EVAL-VELOCITY-RULE
               END-IF
           END-PERFORM
           .

       1100-EVAL-VELOCITY-RULE.
      *    Read the velocity queue for this account
           MOVE LS-ACCT-NUMBER TO WS-TVT-QUEUE-NAME
           EXEC CICS READQ TS
               QUEUE(WS-TVT-QUEUE-NAME)
               INTO(WS-TVT-DATA)
               LENGTH(WS-TVT-LENGTH)
               ITEM(1)
               RESP(WS-RESP)
           END-EXEC

           IF WS-RESP = DFHRESP(NORMAL)
      *        Count transactions within the time window
               MOVE ZERO TO WS-WINDOW-TXN-COUNT
               MOVE ZERO TO WS-WINDOW-TXN-TOTAL
               PERFORM VARYING WS-TVT-IDX FROM 1 BY 1
                   UNTIL WS-TVT-IDX > WS-TVT-ENTRY-COUNT
                   PERFORM 1110-CALC-MINUTES-AGO
                   IF WS-MINUTES-AGO <=
                       WS-FRT-WINDOW(WS-RULE-IDX)
                       ADD 1 TO WS-WINDOW-TXN-COUNT
                       ADD WS-TVT-AMOUNT(WS-TVT-IDX)
                           TO WS-WINDOW-TXN-TOTAL
                   END-IF
               END-PERFORM

      *        Check if threshold is exceeded
               IF WS-WINDOW-TXN-COUNT >=
                   WS-FRT-THRESH-CNT(WS-RULE-IDX)
                   AND WS-WINDOW-TXN-TOTAL >=
                       WS-FRT-THRESH-AMT(WS-RULE-IDX)
                   PERFORM 8000-TRIGGER-RULE
               END-IF
           END-IF
           .

       1110-CALC-MINUTES-AGO.
      *    Calculate minutes between the TVT entry
      *    timestamp and the current time
           EXEC CICS ASKTIME
               ABSTIME(WS-CURRENT-ABSTIME)
           END-EXEC
           COMPUTE WS-MINUTES-AGO =
               (WS-CURRENT-ABSTIME -
                WS-TVT-ABSTIME(WS-TVT-IDX))
               / 60000000
           .

Alert Generation

When a rule triggers, the screening module records an alert in the fraud alert table and determines the appropriate action based on the rule's action code and the cumulative risk score:

       8000-TRIGGER-RULE.
           ADD WS-FRT-RISK-SCORE(WS-RULE-IDX)
               TO WS-CUMULATIVE-RISK
           ADD 1 TO WS-MATCH-COUNT

      *    Record the triggered rule ID
           STRING WS-FRT-RULE-ID(WS-RULE-IDX)
                  DELIMITED SIZE
                  ','
                  DELIMITED SIZE
               INTO LS-TRIGGERED-RULES
               WITH POINTER WS-RULE-PTR
           END-STRING

      *    Escalate action if this rule is more severe
           EVALUATE WS-FRT-ACTION(WS-RULE-IDX)
               WHEN 'B'
                   MOVE 'B' TO WS-MAX-ACTION
               WHEN 'H'
                   IF WS-MAX-ACTION NOT = 'B'
                       MOVE 'H' TO WS-MAX-ACTION
                   END-IF
               WHEN 'F'
                   IF WS-MAX-ACTION = 'A'
                       MOVE 'F' TO WS-MAX-ACTION
                   END-IF
           END-EVALUATE

      *    Write alert record to DB2
           EXEC SQL
               INSERT INTO FRAUD_ALERTS
               (ALERT_ID, ALERT_TIMESTAMP,
                ACCT_NUMBER, CUST_NUMBER,
                RULE_ID, TXN_TYPE, TXN_AMOUNT,
                TXN_CHANNEL, RISK_SCORE,
                ACTION_TAKEN, ALERT_STATUS)
               VALUES
               (NEXT VALUE FOR ALERT_SEQ,
                CURRENT TIMESTAMP,
                :LS-ACCT-NUMBER, :LS-CUST-NUMBER,
                :WS-FRT-RULE-ID(WS-RULE-IDX),
                :LS-TXN-TYPE, :LS-TXN-AMOUNT,
                :LS-TXN-CHANNEL,
                :WS-FRT-RISK-SCORE(WS-RULE-IDX),
                :WS-FRT-ACTION(WS-RULE-IDX),
                'O')
           END-EXEC
           .

Phase 3: Batch Fraud Analysis

Daily Structuring Detection

The batch structuring detection program runs after the daily transaction posting step and before report generation. It aggregates all cash transactions by customer and flags those that appear to be structured to avoid the $10,000 CTR threshold:

      *================================================================*
      * CAPFRSTC - Batch Structuring Detection                         *
      * Processes daily transactions to detect potential structuring    *
      *================================================================*
       IDENTIFICATION DIVISION.
       PROGRAM-ID. CAPFRSTC.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
           COPY CAPSTONE-CONSTANTS.
           COPY CAPSTONE-TRANSACTION.

       01  WS-STRUCT-INDICATORS.
           05  WS-SI-CASH-COUNT       PIC 9(03) VALUE ZERO.
           05  WS-SI-CASH-TOTAL       PIC S9(11)V99 COMP-3
                                      VALUE ZERO.
           05  WS-SI-MAX-TXN          PIC S9(9)V99 COMP-3
                                      VALUE ZERO.
           05  WS-SI-ALL-BELOW-10K    PIC X(01) VALUE 'Y'.
           05  WS-SI-CLOSE-TO-10K     PIC 9(03) VALUE ZERO.

       01  WS-PREV-CUST-NUM          PIC X(10) VALUE SPACES.
       01  WS-STRUCT-THRESHOLD        PIC S9(9)V99 COMP-3
                                      VALUE 10000.00.
       01  WS-CLOSE-RANGE-LOW        PIC S9(9)V99 COMP-3
                                      VALUE 8000.00.
       01  WS-CLOSE-RANGE-HIGH       PIC S9(9)V99 COMP-3
                                      VALUE 9999.99.

       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 1000-INITIALIZE
           PERFORM 2000-PROCESS-TRANSACTIONS
               UNTIL END-OF-FILE
      *    Process the last customer group
           IF WS-PREV-CUST-NUM NOT = SPACES
               PERFORM 3000-EVALUATE-CUSTOMER
           END-IF
           PERFORM 9000-TERMINATE
           STOP RUN.

       2000-PROCESS-TRANSACTIONS.
      *    Control break on customer number
           IF TXN-CUST-NUMBER NOT = WS-PREV-CUST-NUM
               IF WS-PREV-CUST-NUM NOT = SPACES
                   PERFORM 3000-EVALUATE-CUSTOMER
               END-IF
               MOVE TXN-CUST-NUMBER TO WS-PREV-CUST-NUM
               PERFORM 2100-RESET-ACCUMULATORS
           END-IF

      *    Accumulate cash transactions only
           IF (TXN-IS-DEPOSIT OR TXN-IS-WITHDRAWAL)
               AND (TXN-CHANNEL-BRANCH OR TXN-CHANNEL-ATM)
               ADD 1 TO WS-SI-CASH-COUNT
               ADD TXN-AMOUNT TO WS-SI-CASH-TOTAL
               IF TXN-AMOUNT > WS-SI-MAX-TXN
                   MOVE TXN-AMOUNT TO WS-SI-MAX-TXN
               END-IF
               IF TXN-AMOUNT >= WS-STRUCT-THRESHOLD
                   MOVE 'N' TO WS-SI-ALL-BELOW-10K
               END-IF
               IF TXN-AMOUNT >= WS-CLOSE-RANGE-LOW
                   AND TXN-AMOUNT <= WS-CLOSE-RANGE-HIGH
                   ADD 1 TO WS-SI-CLOSE-TO-10K
               END-IF
           END-IF

           PERFORM 2900-READ-NEXT
           .

       3000-EVALUATE-CUSTOMER.
      *    Structuring indicators:
      *    1. Total cash >= $10,000
      *    2. No single transaction >= $10,000
      *    3. Multiple transactions (3+)
      *    4. Transactions clustered near $9,999
           IF WS-SI-CASH-TOTAL >= WS-STRUCT-THRESHOLD
               AND WS-SI-ALL-BELOW-10K = 'Y'
               AND WS-SI-CASH-COUNT >= 3
               MOVE 'HIGH' TO WS-SUSPICION-LEVEL
               PERFORM 3100-WRITE-ALERT
           ELSE IF WS-SI-CLOSE-TO-10K >= 2
               MOVE 'MEDIUM' TO WS-SUSPICION-LEVEL
               PERFORM 3100-WRITE-ALERT
           END-IF
           .

       3100-WRITE-ALERT.
           INITIALIZE WS-ALERT-RECORD
           MOVE WS-PREV-CUST-NUM   TO ALT-CUST-NUMBER
           MOVE 'ST'                TO ALT-ALERT-TYPE
           MOVE WS-SI-CASH-TOTAL   TO ALT-TOTAL-AMOUNT
           MOVE WS-SI-CASH-COUNT   TO ALT-TXN-COUNT
           MOVE WS-SUSPICION-LEVEL TO ALT-SEVERITY
           MOVE WS-PROCESS-DATE    TO ALT-DETECTION-DATE
           MOVE 'CAPFRSTC'         TO ALT-SOURCE-PROGRAM
           WRITE ALERT-RECORD FROM WS-ALERT-RECORD
           ADD 1 TO WS-ALERT-COUNT
           .

Account Takeover Pattern Detection

The batch takeover detection program correlates account maintenance events with subsequent financial transactions. It reads the audit trail for the past 72 hours and flags suspicious sequences:

       4000-CHECK-TAKEOVER-PATTERNS.
           EXEC SQL
               DECLARE TAKEOVER-CSR CURSOR FOR
               SELECT A.CUST_NUMBER,
                      A.EVENT_TYPE,
                      A.EVENT_TIMESTAMP,
                      T.TXN_TYPE,
                      T.TXN_AMOUNT,
                      T.TXN_TIMESTAMP,
                      T.TXN_CHANNEL
               FROM   AUDIT_TRAIL A
               INNER JOIN TRANSACTION_HISTORY T
                   ON A.CUST_NUMBER = T.CUST_NUMBER
               WHERE  A.EVENT_TYPE IN
                   ('ADDR_CHG', 'PWD_RST', 'PHONE_CHG',
                    'EMAIL_CHG', 'NEW_PAYEE')
               AND    A.EVENT_TIMESTAMP >=
                   CURRENT TIMESTAMP - 72 HOURS
               AND    T.TXN_TIMESTAMP > A.EVENT_TIMESTAMP
               AND    T.TXN_AMOUNT >= 1000.00
               AND    T.TXN_TYPE IN ('WD', 'XF', 'WR')
               ORDER BY A.CUST_NUMBER, A.EVENT_TIMESTAMP
               FOR FETCH ONLY
           END-EXEC

           EXEC SQL OPEN TAKEOVER-CSR END-EXEC

           PERFORM UNTIL SQLCODE NOT = 0
               EXEC SQL
                   FETCH TAKEOVER-CSR
                   INTO :HV-CUST-NUM, :HV-EVENT-TYPE,
                        :HV-EVENT-TS, :HV-TXN-TYPE,
                        :HV-TXN-AMT, :HV-TXN-TS,
                        :HV-TXN-CHANNEL
               END-EXEC
               IF SQLCODE = 0
                   PERFORM 4100-SCORE-TAKEOVER-RISK
               END-IF
           END-PERFORM

           EXEC SQL CLOSE TAKEOVER-CSR END-EXEC
           .

       4100-SCORE-TAKEOVER-RISK.
      *    Score based on event-to-transaction pattern
           MOVE ZERO TO WS-TAKEOVER-SCORE

      *    Address change followed by large withdrawal
           IF HV-EVENT-TYPE = 'ADDR_CHG'
               AND (HV-TXN-TYPE = 'WD'
                   OR HV-TXN-TYPE = 'WR')
               ADD 40 TO WS-TAKEOVER-SCORE
           END-IF

      *    Password reset followed by wire transfer
           IF HV-EVENT-TYPE = 'PWD_RST'
               AND HV-TXN-TYPE = 'WR'
               ADD 50 TO WS-TAKEOVER-SCORE
           END-IF

      *    New payee followed by transfer
           IF HV-EVENT-TYPE = 'NEW_PAYEE'
               AND HV-TXN-TYPE = 'XF'
               ADD 35 TO WS-TAKEOVER-SCORE
           END-IF

      *    Multiple indicators amplify risk
           IF HV-TXN-AMT >= 5000.00
               ADD 20 TO WS-TAKEOVER-SCORE
           END-IF
           IF HV-TXN-CHANNEL = 'OL'
               ADD 10 TO WS-TAKEOVER-SCORE
           END-IF

      *    Generate alert if score exceeds threshold
           IF WS-TAKEOVER-SCORE >= 50
               MOVE 'HIGH' TO WS-ALERT-SEVERITY
               PERFORM 4200-WRITE-TAKEOVER-ALERT
           ELSE IF WS-TAKEOVER-SCORE >= 30
               MOVE 'MEDIUM' TO WS-ALERT-SEVERITY
               PERFORM 4200-WRITE-TAKEOVER-ALERT
           END-IF
           .

Phase 4: Suspicious Activity Reporting (SAR)

Regulatory Requirements

When the fraud detection system identifies activity that meets the criteria for a Suspicious Activity Report, the compliance team must file a SAR with FinCEN within 30 days. The capstone extension generates a SAR preparation file that the compliance team reviews and submits through the BSA E-Filing system.

The SAR extract record contains the fields mandated by FinCEN:

       01  WS-SAR-RECORD.
           05  SAR-FILING-TYPE        PIC X(01).
               88  SAR-INITIAL        VALUE 'I'.
               88  SAR-CONTINUING     VALUE 'C'.
               88  SAR-JOINT          VALUE 'J'.
           05  SAR-FILER-INFO.
               10  SAR-FILER-NAME     PIC X(40).
               10  SAR-FILER-TIN      PIC X(09).
               10  SAR-FILER-ADDRESS  PIC X(80).
               10  SAR-FILER-RSSD     PIC X(10).
           05  SAR-SUBJECT-INFO.
               10  SAR-SUBJ-NAME      PIC X(50).
               10  SAR-SUBJ-SSN-TIN   PIC X(09).
               10  SAR-SUBJ-DOB       PIC X(10).
               10  SAR-SUBJ-ADDRESS   PIC X(80).
               10  SAR-SUBJ-ID-TYPE   PIC X(02).
               10  SAR-SUBJ-ID-NUM    PIC X(20).
               10  SAR-SUBJ-PHONE     PIC X(12).
               10  SAR-SUBJ-OCCUPATION PIC X(30).
           05  SAR-SUSPICIOUS-ACTIVITY.
               10  SAR-SA-DATE-FROM   PIC X(10).
               10  SAR-SA-DATE-TO     PIC X(10).
               10  SAR-SA-TOTAL-AMT   PIC S9(13)V99 COMP-3.
               10  SAR-SA-CATEGORIES.
                   15  SAR-CAT-STRUCTURING PIC X(01).
                   15  SAR-CAT-TERRORISM   PIC X(01).
                   15  SAR-CAT-FRAUD       PIC X(01).
                   15  SAR-CAT-GAMING      PIC X(01).
                   15  SAR-CAT-LAUNDERING  PIC X(01).
                   15  SAR-CAT-ID-THEFT    PIC X(01).
                   15  SAR-CAT-OTHER       PIC X(01).
               10  SAR-SA-NARRATIVE   PIC X(500).
           05  SAR-ACCOUNT-INFO OCCURS 5 TIMES.
               10  SAR-AI-ACCT-NUM    PIC X(10).
               10  SAR-AI-ACCT-TYPE   PIC X(01).
               10  SAR-AI-TXN-COUNT   PIC 9(05).
               10  SAR-AI-TXN-TOTAL   PIC S9(11)V99 COMP-3.

SAR Generation Batch Program

The SAR generation program reads the fraud alert table, correlates alerts with customer and transaction data, and produces SAR preparation records for compliance review:

       5000-GENERATE-SAR-CANDIDATES.
           EXEC SQL
               DECLARE SAR-CSR CURSOR FOR
               SELECT DISTINCT
                   A.CUST_NUMBER,
                   C.CUST_LAST_NAME,
                   C.CUST_FIRST_NAME,
                   C.CUST_SSN,
                   C.CUST_DOB,
                   MIN(A.ALERT_TIMESTAMP) AS FIRST_ALERT,
                   MAX(A.ALERT_TIMESTAMP) AS LAST_ALERT,
                   SUM(A.TXN_AMOUNT) AS TOTAL_AMOUNT,
                   COUNT(*) AS ALERT_COUNT,
                   MAX(A.RISK_SCORE) AS MAX_RISK
               FROM   FRAUD_ALERTS A
               INNER JOIN CUSTOMER C
                   ON A.CUST_NUMBER = C.CUST_NUMBER
               WHERE  A.ALERT_STATUS = 'O'
               AND    A.RISK_SCORE >= 50
               AND    A.ALERT_TIMESTAMP >=
                   CURRENT TIMESTAMP - 30 DAYS
               GROUP BY A.CUST_NUMBER,
                   C.CUST_LAST_NAME,
                   C.CUST_FIRST_NAME,
                   C.CUST_SSN, C.CUST_DOB
               HAVING COUNT(*) >= 2
                   OR MAX(A.RISK_SCORE) >= 80
               ORDER BY MAX(A.RISK_SCORE) DESC
               FOR FETCH ONLY
           END-EXEC
           .

       5100-BUILD-SAR-RECORD.
      *    Populate the filer information (the bank)
           MOVE 'COMMUNITY BANK' TO SAR-FILER-NAME
           MOVE '12-3456789'     TO SAR-FILER-TIN

      *    Populate the subject information
           STRING HV-CUST-LNAME DELIMITED SPACE
                  ', '           DELIMITED SIZE
                  HV-CUST-FNAME DELIMITED SPACE
               INTO SAR-SUBJ-NAME
           END-STRING
           MOVE HV-CUST-SSN TO SAR-SUBJ-SSN-TIN
           MOVE HV-CUST-DOB TO SAR-SUBJ-DOB

      *    Populate the activity information
           MOVE HV-FIRST-ALERT TO SAR-SA-DATE-FROM
           MOVE HV-LAST-ALERT  TO SAR-SA-DATE-TO
           MOVE HV-TOTAL-AMT   TO SAR-SA-TOTAL-AMT

      *    Set activity categories based on alert types
           PERFORM 5200-SET-SAR-CATEGORIES

      *    Build narrative from alert details
           PERFORM 5300-BUILD-NARRATIVE
           .

Compliance Dashboard Report

The batch cycle produces a daily compliance summary report that the compliance officer reviews each morning:

       6000-PRINT-COMPLIANCE-SUMMARY.
           WRITE RPT-RECORD FROM WS-TITLE-LINE
               AFTER ADVANCING PAGE

           MOVE WS-TOTAL-ALERTS    TO RPT-ALERT-COUNT
           MOVE WS-HIGH-ALERTS     TO RPT-HIGH-COUNT
           MOVE WS-MEDIUM-ALERTS   TO RPT-MED-COUNT
           MOVE WS-LOW-ALERTS      TO RPT-LOW-COUNT
           MOVE WS-BLOCKED-TXNS    TO RPT-BLOCKED-COUNT
           MOVE WS-HELD-TXNS       TO RPT-HELD-COUNT
           MOVE WS-SAR-CANDIDATES  TO RPT-SAR-COUNT

           WRITE RPT-RECORD FROM WS-SUMMARY-SECTION
               AFTER ADVANCING 2 LINES

      *    Top 10 highest-risk customers
           WRITE RPT-RECORD FROM WS-TOP-RISK-HEADER
               AFTER ADVANCING 2 LINES

           PERFORM VARYING WS-TOP-IDX FROM 1 BY 1
               UNTIL WS-TOP-IDX > 10
                  OR WS-TOP-IDX > WS-TOP-RISK-COUNT
               MOVE WS-TOP-CUST(WS-TOP-IDX)
                   TO RPT-TOP-CUST-NUM
               MOVE WS-TOP-NAME(WS-TOP-IDX)
                   TO RPT-TOP-CUST-NAME
               MOVE WS-TOP-SCORE(WS-TOP-IDX)
                   TO RPT-TOP-SCORE
               MOVE WS-TOP-ALERT-CT(WS-TOP-IDX)
                   TO RPT-TOP-ALERT-CT
               MOVE WS-TOP-AMOUNT(WS-TOP-IDX)
                   TO RPT-TOP-AMOUNT
               WRITE RPT-RECORD FROM WS-TOP-RISK-LINE
                   AFTER ADVANCING 1 LINE
           END-PERFORM

      *    Rule effectiveness statistics
           WRITE RPT-RECORD FROM WS-RULE-STATS-HEADER
               AFTER ADVANCING 2 LINES

           PERFORM VARYING WS-RULE-IDX FROM 1 BY 1
               UNTIL WS-RULE-IDX > WS-ACTIVE-RULE-COUNT
               MOVE WS-RULE-ID(WS-RULE-IDX)
                   TO RPT-RULE-ID
               MOVE WS-RULE-NAME(WS-RULE-IDX)
                   TO RPT-RULE-NAME
               MOVE WS-RULE-TRIGGERS(WS-RULE-IDX)
                   TO RPT-RULE-TRIGGERS
               MOVE WS-RULE-CONFIRMS(WS-RULE-IDX)
                   TO RPT-RULE-CONFIRMS
               IF WS-RULE-TRIGGERS(WS-RULE-IDX) > 0
                   COMPUTE RPT-RULE-ACCURACY =
                       WS-RULE-CONFIRMS(WS-RULE-IDX) *
                       100 /
                       WS-RULE-TRIGGERS(WS-RULE-IDX)
               ELSE
                   MOVE ZERO TO RPT-RULE-ACCURACY
               END-IF
               WRITE RPT-RECORD FROM WS-RULE-STATS-LINE
                   AFTER ADVANCING 1 LINE
           END-PERFORM
           .

Results and Operational Impact

The fraud detection module was deployed in a phased rollout over three months. During the first month, all rules operated in test mode (FR-TEST-MODE), generating alerts without blocking transactions, allowing the compliance team to calibrate thresholds and eliminate false positives.

Detection effectiveness: In the first quarter of production operation, the system generated 847 alerts, of which 123 (14.5%) were confirmed as actual fraud or suspicious activity. The structuring detection identified 18 customers engaging in deliberate structuring, resulting in 12 SAR filings. The velocity detection blocked 34 ATM fraud attempts totaling $47,000 in prevented losses. The account takeover detection flagged 8 compromised accounts, all of which were confirmed by subsequent investigation.

False positive management: The initial false positive rate of 85.5% was higher than desired but consistent with industry norms for rule-based systems. The team tuned the thresholds over three months, increasing the confirmation rate to 22% by the end of the second quarter. The most effective tuning was raising the structuring detection minimum from 3 transactions to 4 and narrowing the "close to $10K" range from $8,000-$9,999 to $9,000-$9,999.

Regulatory compliance: The automated SAR preparation reduced the compliance team's SAR filing time from an average of 4 hours per report (manual data gathering and narrative writing) to 45 minutes (review and approval of system-generated content). The bank filed 31 SARs in the first two quarters, up from 8 in the prior year, demonstrating improved detection rather than increased fraud.

Performance impact: The real-time screening added an average of 12 milliseconds to each CICS transaction, well within acceptable limits. The batch fraud analysis programs added 18 minutes to the nightly batch cycle. Both impacts were considered negligible by the operations team.


Discussion Questions

  1. The rule-based approach allows compliance staff to modify detection thresholds without code changes. What are the risks of making this too easy? How would you implement a change control process for fraud rules?

  2. The false positive rate of 85.5% means that most alerts require human review with no resulting action. How would you design a machine-learning feedback loop within the COBOL system to improve accuracy over time?

  3. The real-time velocity check uses CICS Temporary Storage queues to hold recent transaction data. What happens if the CICS region is recycled? How would you make the velocity data persistent without sacrificing performance?

  4. The SAR narrative field is currently populated by concatenating alert descriptions. A regulatory examiner expects a coherent, human-readable narrative. How would you design a template-based narrative generator in COBOL?

  5. As the bank grows to multiple branches (as described in Case Study 1), how does the fraud detection architecture need to change? Consider cross-branch structuring, multi-branch velocity patterns, and branch-level risk scoring.