Case Study 2: Batch Report Generation at County Tax Office

Background

The Maricopa County Tax Assessor's Office maintains property tax records for over 1.5 million parcels. Each quarter, the office generates a property tax assessment report that is mailed to property owners and archived for auditing. The report system, originally written in COBOL in 1987, processes data from a VSAM file and produces a formatted print report.

For this case study, we focus on the report formatting logic -- the part of the program that takes processed data and produces neatly formatted output. We simulate this using DISPLAY statements and hardcoded data, demonstrating the patterns that production batch report programs use to generate thousands of pages of formatted output.

The Report Format

The County Tax Assessment Report follows a standard header-detail-footer pattern with page breaks:

================================================================================
                    MARICOPA COUNTY PROPERTY TAX ASSESSMENT
                         QUARTERLY REPORT - Q4 2026
                    DATE: 02/10/2026          PAGE:     1
================================================================================

  PARCEL ID      OWNER NAME                 ADDRESS                    ASSESSED
  -----------    -------------------------  -------------------------  ----------
  07-301-0042    JOHNSON, ROBERT M.         1425 E CAMELBACK RD        $  385,200
  07-301-0043    MARTINEZ, ELENA            1431 E CAMELBACK RD        $  372,500
  07-301-0044    CHEN, DAVID & LINDA        1437 E CAMELBACK RD        $  410,750
  07-301-0045    PATEL, ARUN K.             1443 E CAMELBACK RD        $  398,000
  07-301-0046    WILLIAMS, SARAH J.         1449 E CAMELBACK RD        $  425,300
                                                                       ----------
                                     District 07-301 Subtotal:         $1,991,750
                                     Parcels in District:                       5

================================================================================
                    MARICOPA COUNTY PROPERTY TAX ASSESSMENT
                         QUARTERLY REPORT - Q4 2026
                    DATE: 02/10/2026          PAGE:     2
================================================================================
  ...
================================================================================
                              GRAND TOTALS
  Total Parcels Assessed:                                                    127
  Total Assessed Value:                                            $48,752,300.00
  Average Assessment:                                                 $383,876.38
  Report Generated:  02/10/2026  14:30:22
  Processing Time:   3.45 seconds
================================================================================

Design Patterns

Every COBOL batch report follows this fundamental structure:

Program Flow:
  1. Print report header (title, date, column headers)
  2. For each record:
     a. Check if page is full -> if yes, eject page and reprint header
     b. Format the detail line
     c. Print the detail line
     d. Accumulate totals
     e. Check for control breaks (district change)
  3. Print final totals (footer)

In COBOL, this translates to:

       PROCEDURE DIVISION.
       MAIN-PROGRAM.
           PERFORM 100-INITIALIZE
           PERFORM 200-PRINT-PAGE-HEADER
           PERFORM 300-PROCESS-RECORDS
               VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > WS-RECORD-COUNT
           PERFORM 500-PRINT-DISTRICT-SUBTOTAL
           PERFORM 600-PRINT-GRAND-TOTALS
           STOP RUN.

Pattern 2: Structured Report Lines

Each type of output line is defined as a structured record in WORKING-STORAGE. This guarantees consistent column alignment across all lines:

      * Page header line 1 (title)
       01  WS-TITLE-LINE.
           05  FILLER     PIC X(20) VALUE SPACES.
           05  FILLER     PIC X(42)
               VALUE "MARICOPA COUNTY PROPERTY TAX ASSESSMENT".
           05  FILLER     PIC X(18) VALUE SPACES.

      * Column headers
       01  WS-COL-HEADER.
           05  FILLER     PIC X(2)  VALUE SPACES.
           05  FILLER     PIC X(14) VALUE "PARCEL ID".
           05  FILLER     PIC X(2)  VALUE SPACES.
           05  FILLER     PIC X(27) VALUE "OWNER NAME".
           05  FILLER     PIC X(2)  VALUE SPACES.
           05  FILLER     PIC X(25) VALUE "ADDRESS".
           05  FILLER     PIC X(2)  VALUE SPACES.
           05  FILLER     PIC X(10) VALUE "ASSESSED".

      * Detail line (one per property)
       01  WS-DETAIL-LINE.
           05  FILLER        PIC X(2) VALUE SPACES.
           05  WS-DET-PARCEL PIC X(12).
           05  FILLER        PIC X(4) VALUE SPACES.
           05  WS-DET-OWNER  PIC X(25).
           05  FILLER        PIC X(2) VALUE SPACES.
           05  WS-DET-ADDR   PIC X(25).
           05  FILLER        PIC X(2) VALUE SPACES.
           05  WS-DET-AMOUNT PIC $$$,$$$,MATH1$,$$$,$$9.99.

       MOVE WS-AMOUNT TO WS-AMT-EDITED
      * WS-AMT-EDITED = "   $385,200.00"

For the grand total, a wider edited picture is needed:

```cobol 01 WS-GRAND-TOTAL PIC 9(12)V99. 01 WS-GRAND-EDITED PIC $$,$$$,MATH4$,$$9.99. ``` ### Pattern 6: Processing Statistics Professional reports include processing statistics: ```cobol 01 WS-START-TIME PIC 9(8). 01 WS-END-TIME PIC 9(8). 01 WS-ELAPSED-SECS PIC 9(6)V99. ACCEPT WS-START-TIME FROM TIME *> ... process all records ... ACCEPT WS-END-TIME FROM TIME COMPUTE WS-ELAPSED-SECS = ((WS-END-HH * 3600) + (WS-END-MM * 60) + WS-END-SS + (WS-END-HS / 100)) - ((WS-START-HH * 3600) + (WS-START-MM * 60) + WS-START-SS + (WS-START-HS / 100)) DISPLAY "Processing Time: " WS-ELAPSED-SECS " seconds" ``` ## Implementation Structure The complete program structure follows standard batch report conventions: ```cobol PROCEDURE DIVISION. 0000-MAIN-CONTROL. PERFORM 1000-INITIALIZE PERFORM 2000-PROCESS-ALL-RECORDS PERFORM 3000-FINALIZE STOP RUN. 1000-INITIALIZE. ACCEPT WS-SYS-DATE FROM DATE YYYYMMDD ACCEPT WS-START-TIME FROM TIME PERFORM FORMAT-DATE PERFORM LOAD-SAMPLE-DATA PERFORM PRINT-PAGE-HEADER. 2000-PROCESS-ALL-RECORDS. PERFORM VARYING WS-IDX FROM 1 BY 1 UNTIL WS-IDX > WS-RECORD-COUNT PERFORM CHECK-PAGE-BREAK PERFORM CHECK-CONTROL-BREAK PERFORM FORMAT-DETAIL-LINE PERFORM PRINT-DETAIL-LINE PERFORM ACCUMULATE-TOTALS END-PERFORM PERFORM PRINT-LAST-SUBTOTAL. 3000-FINALIZE. ACCEPT WS-END-TIME FROM TIME PERFORM COMPUTE-ELAPSED-TIME PERFORM PRINT-GRAND-TOTALS PERFORM PRINT-STATISTICS. ``` ## Production Considerations ### JCL for Batch Report Jobs In a production IBM mainframe environment, this program would be submitted through JCL: ```jcl //TAXRPT JOB (ACCT),'TAX ASSESSMENT RPT',CLASS=A //STEP01 EXEC PGM=TAXASSMT //STEPLIB DD DSN=TAX.LOAD.LIBRARY,DISP=SHR //SYSOUT DD SYSOUT=* //TAXFILE DD DSN=TAX.PARCEL.MASTER,DISP=SHR //REPORT DD DSN=TAX.QUARTERLY.REPORT, // DISP=(NEW,CATLG,DELETE), // DCB=(RECFM=FBA,LRECL=133,BLKSIZE=0), // SPACE=(CYL,(5,2),RLSE) //SYSIN DD DUMMY ``` The REPORT DD would be the output file where WRITE statements send the report lines. The LRECL of 133 includes 1 byte for carriage control (ASA) and 132 bytes for the print line. ### Carriage Control Production COBOL print reports use ASA carriage control characters in the first byte of each print line: | Character | Action | |-----------|--------| | (space) | Single space before printing (normal) | | 0 | Double space before printing | | - | Triple space before printing | | + | No space (overprint on same line) | | 1 | Skip to top of next page | ```cobol 01 WS-PRINT-LINE. 05 WS-CC PIC X VALUE SPACE. 05 WS-PRINT-DATA PIC X(132). * Normal line MOVE SPACE TO WS-CC WRITE REPORT-RECORD FROM WS-PRINT-LINE * Page break MOVE "1" TO WS-CC WRITE REPORT-RECORD FROM WS-PRINT-LINE ``` In our console prototype, we simulate page breaks with blank lines and border reprints. ### Report Distribution In production, the generated report would be: - Sent to a high-speed printer for physical mailing - Archived in a document management system - Converted to PDF for electronic distribution - Stored as a GDG (Generation Data Group) for historical reference ## The Relationship to File I/O This case study intentionally uses DISPLAY statements instead of file WRITE statements to demonstrate the formatting logic independently of file I/O mechanics. In Part III (Chapters 11-16), you will learn to: 1. Write these formatted lines to sequential files using WRITE statements 2. Read input data from files instead of hardcoded tables 3. Use FD (File Description) entries to define print files 4. Implement proper carriage control for print output 5. Handle file status codes and error conditions The formatting logic -- the record structures, edited pictures, page break counting, control break detection, and accumulator patterns -- remains identical whether the output goes to the console or to a file. ## Exercises for This Case Study 1. Implement the complete report program using the patterns described above. Use hardcoded data for at least 20 property records across 3 districts. 2. Add a second level of control break: county. Print county subtotals in addition to district subtotals. 3. Add a "tax rate" column that calculates property tax at 1.25% of the assessed value. Include the tax amount in subtotals and grand totals. 4. Implement the page break logic with a configurable lines-per-page constant. Test with values of 10, 15, and 20. 5. Add an "exception report" section at the end that lists any properties with an assessed value above $500,000, flagged as "HIGH VALUE ASSESSMENT". ## Discussion Questions 1. Why does COBOL define each report line as a separate record structure in WORKING-STORAGE rather than building lines dynamically with STRING? 2. What are the advantages of the control break pattern over alternative approaches (such as sorting and grouping in SQL)? 3. In a report that spans hundreds of pages, why is page break management in the application code rather than in the printer driver? 4. How would this report change if it needed to support multiple output formats (print, PDF, CSV) from the same program? 5. The report uses edited PICTURE clauses like `$$$,MATH69.99`. What would happen if an assessed value exceeded the picture capacity (for example, $1 billion)? How should the program handle this?