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
Pattern 1: The Header-Detail-Footer Structure
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 `$$$, MATH6 9.99`. What would happen if an assessed value exceeded the picture capacity (for example, $1 billion)? How should the program handle this?