feat: add benchmark-programs — 58 telecom COBOL test programs

作为子目录纳入系统,与核心测试管道协同

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
NB-076
2026-06-25 09:53:21 +08:00
parent 50f9f0f52f
commit 94400d50d4
278 changed files with 44125 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# 21-csv-fb-lf: CSV to Fixed-Block Conversion (with Line Feed)
## 电信业务场景
外部CDR CSV取込(有LF)。读取含改行的CSV格式CDR文件,通过INSPECT REPLACING将改行展开为多条固定长记录。
## Description
Reads a LINE SEQUENTIAL CSV file where Field3 may contain pipe ('|')
markers that indicate record-splitting points. Uses INSPECT TALLYING
to count markers and INSPECT REPLACING to transform them, then writes
one output record per segment. One CSV input line can produce multiple
output records ("line feed expansion").
## Record Layout
### Input (LINE SEQUENTIAL CSV)
| Field | Description |
|--------|--------------------------------------|
| FIELD1 | Key code |
| FIELD2 | Group identifier |
| FIELD3 | Data values (may contain '|' markers) |
Field3 with '|' markers:
```
ITEM1|ITEM2|ITEM3 -> produces 3 output records
```
### Output (fixed-length: 100 bytes)
Format: `FIELD1|FIELD2|SEGMENT` padded to 100 bytes.
## Files
| File | Purpose |
|------------------------|--------------------------------|
| main-21-csv-fb-lf.cbl | Main COBOL program |
| data-gen.sh | Generate CSV test data |
| run.sh | Compile, run, verify |
| README.md | This file |
## Tests
| Test Case | Description |
|-------------------------|-------------------------------------|
| No pipe markers | 1:1 mapping, no expansion |
| Single pipe | 1 input line -> 2 output records |
| Double pipe | 1 input line -> 3 output records |
| Empty field3 | Empty field handling |
| Leading pipe | Leading empty segment |
| Trailing pipe | Trailing empty segment |
| Multiple segments | Many markers in one field |
| Quoted pipe | Pipe within quotes (literal) |
## Usage
```bash
cd 21-csv-fb-lf
bash data-gen.sh
bash run.sh
```
## Expected Behavior
- Each '|' separator produces an additional output record.
- INSPECT TALLYING counts the markers.
- INSPECT REPLACING transforms markers for processing.
- Output records are fixed-length (100 bytes).
@@ -0,0 +1,731 @@
*> ============================================================
*> 21-csv-fb-lf : CDR CSV取込(有LF) (CDR Import with LF)
*> Input : FILE-IN (file-in.csv: 外部CDR CSV有LF)
*> Output: FILE-OUT (file-out.dat: LF展开后固定长记录)
*> Error : ERROR-REPORT (csv-lf-errors.dat: CSV错误明细)
*> Audit : AUDIT-FILE (csv-lf-audit.dat: 审计跟踪)
*> Coverage: CF-N002~N006, CF-A001, CF-E001~E005
*> SECTION structure:
*> 1000-INIT / 2000-OPEN-FILES / 3000-READ-INPUT
*> 3100-VALIDATE-REC / 3200-PROCESS-REC / 3300-WRITE-OUTPUT
*> 4000-REPORT / 5000-AUDIT / 6000-ERROR-HANDLE / 9000-EXIT
*> ============================================================
IDENTIFICATION DIVISION.
PROGRAM-ID. CsvFbLf.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT FILE-IN ASSIGN TO 'file-in.csv'
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-FILE-IN-STATUS.
SELECT FILE-OUT ASSIGN TO 'file-out.dat'
ORGANIZATION IS SEQUENTIAL
FILE STATUS IS WS-FILE-OUT-STATUS.
SELECT ERROR-REPORT ASSIGN TO 'csv-lf-errors.dat'
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-ERR-STATUS.
SELECT AUDIT-FILE ASSIGN TO 'csv-lf-audit.dat'
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-AUDIT-STATUS.
DATA DIVISION.
FILE SECTION.
FD FILE-IN.
01 IN-LINE PIC X(200).
FD FILE-OUT.
01 OUT-REC PIC X(100).
FD ERROR-REPORT.
01 ERR-REC PIC X(200).
FD AUDIT-FILE.
01 AUD-REC PIC X(200).
WORKING-STORAGE SECTION.
*> Telecom CDR record layout
01 WS-TELECOM-REC.
COPY "telecom/TEL-CDR.cpy".
*> File status fields
01 WS-FILE-IN-STATUS PIC X(02).
01 WS-FILE-OUT-STATUS PIC X(02).
01 WS-ERR-STATUS PIC X(02).
01 WS-AUDIT-STATUS PIC X(02).
*> File open indicators
01 WS-FILE-IN-OPEN PIC X(01) VALUE 'N'.
88 WS-FILE-IN-IS-OPEN VALUE 'Y' FALSE 'N'.
01 WS-FILE-OUT-OPEN PIC X(01) VALUE 'N'.
88 WS-FILE-OUT-IS-OPEN VALUE 'Y' FALSE 'N'.
01 WS-ERR-REP-OPEN PIC X(01) VALUE 'N'.
88 WS-ERR-REP-IS-OPEN VALUE 'Y' FALSE 'N'.
01 WS-AUDIT-OPEN PIC X(01) VALUE 'N'.
88 WS-AUDIT-IS-OPEN VALUE 'Y' FALSE 'N'.
*> EOF flag (original)
01 WS-EOF PIC X(01) VALUE 'N'.
88 WS-EOF-YES VALUE 'Y' FALSE 'N'.
*> Original counters and fields
01 WS-REC-COUNT PIC 9(05) VALUE ZERO.
01 WS-LINE-COUNT PIC 9(05) VALUE ZERO.
01 WS-SEG-TOTAL PIC 9(05) VALUE ZERO.
01 WS-FIELD1 PIC X(30).
01 WS-FIELD2 PIC X(30).
01 WS-FIELD3 PIC X(100).
*> INSPECT and segment fields (original)
01 WS-PIPE-COUNT PIC 9(02).
01 WS-SEGMENTS.
05 WS-SEGMENT OCCURS 20 TIMES PIC X(50).
01 WS-SEG-COUNT PIC 9(02).
01 WS-SEG-IDX PIC 9(02).
01 WS-POS PIC 9(03).
01 WS-END-POS PIC 9(03).
01 WS-SEG-LEN PIC 9(03).
*> Output assembly (original)
01 WS-OUT-LINE PIC X(100).
*> Timestamp fields
01 WS-TIMESTAMP PIC X(20).
01 WS-CUR-DATE PIC X(08).
01 WS-CUR-TIME PIC X(08).
01 WS-DISP-TS.
05 WS-DISP-DATE PIC X(08).
05 WS-DISP-DASH PIC X(01) VALUE '-'.
05 WS-DISP-TIME PIC X(06).
*> Error severity constants
01 WS-SEV-I PIC X(01) VALUE 'I'.
01 WS-SEV-W PIC X(01) VALUE 'W'.
01 WS-SEV-E PIC X(01) VALUE 'E'.
01 WS-SEV-C PIC X(01) VALUE 'C'.
*> Extended counters
01 WS-TOTAL-INPUT PIC 9(09) VALUE 0.
01 WS-TOTAL-OUTPUT PIC 9(09) VALUE 0.
01 WS-ERR-TOTAL PIC 9(09) VALUE 0.
01 WS-MALFORMED-CNT PIC 9(09) VALUE 0.
01 WS-TRUNC-CNT PIC 9(09) VALUE 0.
01 WS-ERR-REP-WRITTEN PIC 9(09) VALUE 0.
01 WS-FIELD-COUNT-ERR PIC 9(09) VALUE 0.
01 WS-INFO-COUNT PIC 9(09) VALUE 0.
01 WS-WARN-COUNT PIC 9(09) VALUE 0.
01 WS-ERR-SEV-COUNT PIC 9(09) VALUE 0.
01 WS-CRIT-COUNT PIC 9(09) VALUE 0.
*> Hash total (batch control)
01 WS-HASH-TOTAL PIC 9(15) VALUE 0.
01 WS-HASH-FIELD-LEN PIC 9(05) VALUE 0.
*> Validation control
01 WS-VALID-FLAG PIC X(01) VALUE 'Y'.
88 WS-VALID-RECORD VALUE 'Y' FALSE 'N'.
88 WS-INVALID-RECORD VALUE 'N'.
01 WS-COMMA-COUNT PIC 9(02).
01 WS-FIELD-COUNT PIC 9(02).
01 WS-EXPECTED-FIELDS PIC 9(02) VALUE 3.
*> Quote pairing validation
01 WS-QUOTE-OPEN-COUNT PIC 9(03).
01 WS-QUOTE-BALANCE PIC S9(03).
01 WS-QUOTE-STATUS PIC X(01) VALUE 'B'.
88 WS-QUOTES-BALANCED VALUE 'B'.
88 WS-QUOTES-UNBALANCED VALUE 'U'.
*> Field length truncation
01 WS-FIELD1-MAX-LEN PIC 9(03) VALUE 030.
01 WS-FIELD2-MAX-LEN PIC 9(03) VALUE 030.
01 WS-FIELD3-MAX-LEN PIC 9(03) VALUE 100.
01 WS-TRUNC-FLAG1 PIC X(01) VALUE 'N'.
88 WS-TRUNCATED-F1 VALUE 'Y' FALSE 'N'.
01 WS-TRUNC-FLAG2 PIC X(01) VALUE 'N'.
88 WS-TRUNCATED-F2 VALUE 'Y' FALSE 'N'.
01 WS-TRUNC-FLAG3 PIC X(01) VALUE 'N'.
88 WS-TRUNCATED-F3 VALUE 'Y' FALSE 'N'.
*> Numeric check for segment 1
01 WS-SEGMENT-BUFFER PIC X(50).
*> Record reconciliation
01 WS-EXPECTED-OUT PIC 9(09).
01 WS-RECON-DIFF PIC S9(09).
01 WS-RECON-STATUS PIC X(01).
88 WS-RECON-MATCH VALUE 'M'.
88 WS-RECON-MISMATCH VALUE 'X'.
*> Error context for 6000
01 WS-ERR-LINE-NUM PIC 9(09).
01 WS-ERR-SEVERITY PIC X(01).
01 WS-ERR-DESC PIC X(80).
*> Scratch fields
01 WS-RPT-COUNT PIC Z(9)9.
01 WS-ALL-NUMERIC PIC X(01).
01 WS-IDX PIC 9(02).
01 WS-CHAR-IDX PIC 9(04).
01 WS-COMMA-POS1 PIC 9(03).
01 WS-COMMA-POS2 PIC 9(03).
01 WS-SUB PIC 9(03).
01 WS-CHAR-CHK PIC X(01).
PROCEDURE DIVISION.
*> ============================================================
*> MAIN SECTION — orchestration
*> ============================================================
MAIN SECTION.
MB-PROCESS.
PERFORM 1000-INIT.
PERFORM 2000-OPEN-FILES.
PERFORM 3000-READ-INPUT.
PERFORM 4000-REPORT.
PERFORM 5000-AUDIT.
PERFORM 9000-EXIT.
STOP RUN.
*> ============================================================
*> 1000-INIT SECTION
*> ============================================================
1000-INIT SECTION.
1000-START.
PERFORM 9900-TIMESTAMP.
DISPLAY '[' WS-TIMESTAMP '] '
'CsvFbLf STARTED — CDR CSV Import with LF'.
DISPLAY '[' WS-TIMESTAMP '] '
'Input: file-in.csv Output: file-out.dat'.
DISPLAY '[' WS-TIMESTAMP '] '
'Error: csv-lf-errors.dat Audit: csv-lf-audit.dat'.
MOVE ZERO TO WS-TOTAL-INPUT WS-TOTAL-OUTPUT WS-ERR-TOTAL
WS-MALFORMED-CNT WS-TRUNC-CNT
WS-ERR-REP-WRITTEN WS-INFO-COUNT WS-WARN-COUNT
WS-ERR-SEV-COUNT WS-CRIT-COUNT
WS-FIELD-COUNT-ERR WS-HASH-TOTAL WS-SEG-TOTAL.
MOVE 'N' TO WS-FILE-IN-OPEN WS-FILE-OUT-OPEN
WS-ERR-REP-OPEN WS-AUDIT-OPEN.
1000-EXIT. EXIT.
*> ============================================================
*> 2000-OPEN-FILES SECTION
*> ============================================================
2000-OPEN-FILES SECTION.
2000-START.
OPEN INPUT FILE-IN.
IF WS-FILE-IN-STATUS NOT = '00'
DISPLAY 'CRITICAL: Cannot open FILE-IN status: '
WS-FILE-IN-STATUS
MOVE 1 TO RETURN-CODE STOP RUN
END-IF.
MOVE 'Y' TO WS-FILE-IN-OPEN.
PERFORM 9900-TIMESTAMP.
DISPLAY '[' WS-TIMESTAMP '] '
'FILE-IN opened, status: ' WS-FILE-IN-STATUS.
OPEN OUTPUT FILE-OUT.
IF WS-FILE-OUT-STATUS NOT = '00'
DISPLAY 'CRITICAL: Cannot open FILE-OUT status: '
WS-FILE-OUT-STATUS
MOVE 1 TO RETURN-CODE PERFORM 9000-EXIT
END-IF.
MOVE 'Y' TO WS-FILE-OUT-OPEN.
PERFORM 9900-TIMESTAMP.
DISPLAY '[' WS-TIMESTAMP '] '
'FILE-OUT opened, status: ' WS-FILE-OUT-STATUS.
OPEN OUTPUT ERROR-REPORT.
IF WS-ERR-STATUS NOT = '00'
DISPLAY 'CRITICAL: Cannot open ERROR-REPORT status: '
WS-ERR-STATUS
MOVE 1 TO RETURN-CODE PERFORM 9000-EXIT
END-IF.
MOVE 'Y' TO WS-ERR-REP-OPEN.
PERFORM 9900-TIMESTAMP.
DISPLAY '[' WS-TIMESTAMP '] '
'ERROR-REPORT opened, status: ' WS-ERR-STATUS.
*> Write error report header
MOVE SPACES TO ERR-REC.
STRING 'CSV LF Error Detail - CsvFbLf ' WS-CUR-DATE
INTO ERR-REC. WRITE ERR-REC.
MOVE 'LINE SEV DESCRIPTION' TO ERR-REC. WRITE ERR-REC.
MOVE SPACES TO ERR-REC. WRITE ERR-REC.
OPEN OUTPUT AUDIT-FILE.
IF WS-AUDIT-STATUS NOT = '00'
DISPLAY 'CRITICAL: Cannot open AUDIT-FILE status: '
WS-AUDIT-STATUS
MOVE 1 TO RETURN-CODE PERFORM 9000-EXIT
END-IF.
MOVE 'Y' TO WS-AUDIT-OPEN.
PERFORM 9900-TIMESTAMP.
DISPLAY '[' WS-TIMESTAMP '] '
'AUDIT-FILE opened, status: ' WS-AUDIT-STATUS.
*> Write audit header
MOVE SPACES TO AUD-REC.
STRING 'CSV LF Audit - CsvFbLf Run: ' WS-CUR-DATE
INTO AUD-REC. WRITE AUD-REC.
MOVE SPACES TO AUD-REC. WRITE AUD-REC.
2000-EXIT. EXIT.
*> ============================================================
*> 3000-READ-INPUT SECTION — main read-loop
*> ============================================================
3000-READ-INPUT SECTION.
3000-LOOP.
PERFORM 9900-TIMESTAMP.
DISPLAY '[' WS-TIMESTAMP '] ' 'Processing CSV records'.
PERFORM UNTIL WS-EOF-YES
READ FILE-IN
AT END SET WS-EOF-YES TO TRUE
NOT AT END
ADD 1 TO WS-LINE-COUNT
ADD 1 TO WS-TOTAL-INPUT
MOVE 'Y' TO WS-VALID-FLAG
MOVE 'B' TO WS-QUOTE-STATUS
PERFORM 3100-VALIDATE-RECORD
IF WS-INVALID-RECORD
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-E TO WS-ERR-SEVERITY
PERFORM 6000-ERROR-HANDLE
ELSE
PERFORM 3200-PROCESS-RECORD
END-IF
END-READ
IF WS-FILE-IN-STATUS NOT = '00'
AND WS-FILE-IN-STATUS NOT = '10'
AND WS-EOF NOT = 'Y'
PERFORM 9900-TIMESTAMP
DISPLAY '[' WS-TIMESTAMP '] '
'ERROR: READ FILE-IN status: '
WS-FILE-IN-STATUS
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-E TO WS-ERR-SEVERITY
STRING 'READ failure status=' WS-FILE-IN-STATUS
INTO WS-ERR-DESC
PERFORM 6000-ERROR-HANDLE
END-IF
END-PERFORM.
3000-EXIT. EXIT.
*> ============================================================
*> 3100-VALIDATE-RECORD SECTION
*> ============================================================
3100-VALIDATE-RECORD SECTION.
3100-START.
*> Field count (commas + 1, expect 3)
MOVE ZERO TO WS-COMMA-COUNT.
INSPECT IN-LINE TALLYING WS-COMMA-COUNT FOR ALL ','.
COMPUTE WS-FIELD-COUNT = WS-COMMA-COUNT + 1.
IF WS-FIELD-COUNT NOT = WS-EXPECTED-FIELDS
MOVE 'N' TO WS-VALID-FLAG
ADD 1 TO WS-FIELD-COUNT-ERR WS-MALFORMED-CNT
WS-ERR-TOTAL WS-ERR-SEV-COUNT
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-E TO WS-ERR-SEVERITY
STRING 'Field count mismatch: expect '
WS-EXPECTED-FIELDS ' got ' WS-FIELD-COUNT
INTO WS-ERR-DESC
DISPLAY '[' WS-TIMESTAMP '] '
'ERROR: line ' WS-LINE-COUNT
' field-count ' WS-FIELD-COUNT
EXIT SECTION
END-IF.
*> Quote pairing validation
MOVE ZERO TO WS-QUOTE-OPEN-COUNT.
INSPECT IN-LINE TALLYING WS-QUOTE-OPEN-COUNT FOR ALL '"'.
DIVIDE WS-QUOTE-OPEN-COUNT BY 2
GIVING WS-QUOTE-BALANCE
REMAINDER WS-QUOTE-BALANCE.
IF WS-QUOTE-BALANCE NOT = 0
MOVE 'U' TO WS-QUOTE-STATUS
ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
STRING 'Unbalanced quotes: count=' WS-QUOTE-OPEN-COUNT
INTO WS-ERR-DESC
PERFORM 6000-ERROR-HANDLE
END-IF.
*> Estimate raw field lengths from CSV for truncation
MOVE ZERO TO WS-COMMA-POS1 WS-COMMA-POS2.
MOVE 0 TO WS-SUB.
PERFORM VARYING WS-CHAR-IDX FROM 1 BY 1
UNTIL WS-CHAR-IDX > 200
IF IN-LINE(WS-CHAR-IDX:1) = ','
ADD 1 TO WS-SUB
IF WS-SUB = 1
MOVE WS-CHAR-IDX TO WS-COMMA-POS1
ELSE
IF WS-SUB = 2
MOVE WS-CHAR-IDX TO WS-COMMA-POS2
EXIT PERFORM
END-IF
END-IF
END-IF
END-PERFORM.
*> Field1 truncation check
IF WS-COMMA-POS1 > 0
COMPUTE WS-SUB = WS-COMMA-POS1 - 1
IF WS-SUB > WS-FIELD1-MAX-LEN
MOVE 'Y' TO WS-TRUNC-FLAG1
ADD 1 TO WS-TRUNC-CNT WS-WARN-COUNT WS-ERR-TOTAL
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
STRING 'Field1 trunc: raw len ' WS-SUB
INTO WS-ERR-DESC
PERFORM 6000-ERROR-HANDLE
END-IF
END-IF.
*> Field2 truncation check
IF WS-COMMA-POS2 > 0 AND WS-COMMA-POS1 > 0
COMPUTE WS-SUB = WS-COMMA-POS2 - WS-COMMA-POS1 - 1
IF WS-SUB > WS-FIELD2-MAX-LEN
MOVE 'Y' TO WS-TRUNC-FLAG2
ADD 1 TO WS-TRUNC-CNT WS-WARN-COUNT WS-ERR-TOTAL
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
STRING 'Field2 trunc: raw len ' WS-SUB
INTO WS-ERR-DESC
PERFORM 6000-ERROR-HANDLE
END-IF
END-IF.
*> Field3 truncation check (rest of line after 2nd comma)
IF WS-COMMA-POS2 > 0
COMPUTE WS-SUB = 200 - WS-COMMA-POS2
IF WS-SUB > WS-FIELD3-MAX-LEN
MOVE 'Y' TO WS-TRUNC-FLAG3
ADD 1 TO WS-TRUNC-CNT WS-WARN-COUNT WS-ERR-TOTAL
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
STRING 'Field3 trunc: raw len ' WS-SUB
INTO WS-ERR-DESC
PERFORM 6000-ERROR-HANDLE
END-IF
END-IF.
3100-EXIT. EXIT.
*> ============================================================
*> 3200-PROCESS-RECORD SECTION
*> Original PROCESS-CSV-LINE logic preserved intact:
*> UNSTRING -> pipe-count -> replace -> segment -> output
*> ============================================================
3200-PROCESS-RECORD SECTION.
3200-START.
*> Unstring CSV into 3 fields
MOVE SPACES TO WS-FIELD1 WS-FIELD2 WS-FIELD3.
UNSTRING IN-LINE DELIMITED BY ','
INTO WS-FIELD1 WS-FIELD2 WS-FIELD3
END-UNSTRING.
*> Char-check Field1
MOVE 'Y' TO WS-ALL-NUMERIC.
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > 30 OR WS-ALL-NUMERIC = 'N'
MOVE WS-FIELD1(WS-IDX:1) TO WS-CHAR-CHK
IF WS-CHAR-CHK NOT = SPACE AND
WS-CHAR-CHK < 'A' AND WS-CHAR-CHK > 'Z' AND
WS-CHAR-CHK < '0' AND WS-CHAR-CHK > '9' AND
WS-CHAR-CHK NOT = '-'
MOVE 'N' TO WS-ALL-NUMERIC
END-IF
END-PERFORM.
IF WS-ALL-NUMERIC = 'N'
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
STRING 'Field1 special chars: "' WS-FIELD1(1:15) '"'
INTO WS-ERR-DESC
ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL
PERFORM 6000-ERROR-HANDLE
END-IF.
*> Post-UNSTRING truncation detection
IF FUNCTION LENGTH(FUNCTION TRIM(WS-FIELD1)) > 30
ADD 1 TO WS-TRUNC-CNT
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
MOVE 'Field1 truncated by UNSTRING' TO WS-ERR-DESC
ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL
PERFORM 6000-ERROR-HANDLE
END-IF.
IF FUNCTION LENGTH(FUNCTION TRIM(WS-FIELD2)) > 30
ADD 1 TO WS-TRUNC-CNT
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
MOVE 'Field2 truncated by UNSTRING' TO WS-ERR-DESC
ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL
PERFORM 6000-ERROR-HANDLE
END-IF.
*> Count pipe markers in field3 using INSPECT TALLYING
MOVE ZERO TO WS-PIPE-COUNT.
INSPECT WS-FIELD3 TALLYING WS-PIPE-COUNT FOR ALL '|'.
*> Replace pipes with spaces for clean segment extraction
INSPECT WS-FIELD3 REPLACING ALL '|' BY SPACE.
*> Split field3 at original pipe positions into segments
MOVE ZERO TO WS-SEG-COUNT.
MOVE SPACES TO WS-SEGMENTS.
UNSTRING WS-FIELD3 DELIMITED BY ALL SPACES
INTO WS-SEGMENT(1) WS-SEGMENT(2) WS-SEGMENT(3)
WS-SEGMENT(4) WS-SEGMENT(5) WS-SEGMENT(6)
WS-SEGMENT(7) WS-SEGMENT(8) WS-SEGMENT(9)
WS-SEGMENT(10)
COUNT IN WS-SEG-COUNT
END-UNSTRING.
*> If no pipes, write one record
IF WS-PIPE-COUNT = 0
MOVE 1 TO WS-SEG-COUNT
MOVE WS-FIELD3 TO WS-SEGMENT(1)
END-IF.
*> Write one output record per segment
IF WS-SEG-COUNT = 0
MOVE 1 TO WS-SEG-COUNT
END-IF.
*> Validate first segment numeric
IF WS-SEG-COUNT >= 1
MOVE SPACES TO WS-SEGMENT-BUFFER
MOVE WS-SEGMENT(1) TO WS-SEGMENT-BUFFER
IF WS-SEGMENT-BUFFER NOT IS NUMERIC
AND WS-SEGMENT-BUFFER > SPACES
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-W TO WS-ERR-SEVERITY
STRING 'Non-numeric segment 1: "'
WS-SEGMENT(1)(1:15) '"'
INTO WS-ERR-DESC
ADD 1 TO WS-WARN-COUNT WS-ERR-TOTAL
PERFORM 6000-ERROR-HANDLE
END-IF
END-IF.
PERFORM VARYING WS-SEG-IDX FROM 1 BY 1
UNTIL WS-SEG-IDX > WS-SEG-COUNT OR WS-SEG-IDX > 10
IF WS-SEGMENT(WS-SEG-IDX) > SPACES
PERFORM 3300-WRITE-OUTPUT
END-IF
END-PERFORM.
3200-EXIT. EXIT.
*> ============================================================
*> 3300-WRITE-OUTPUT SECTION
*> ============================================================
3300-WRITE-OUTPUT SECTION.
3300-START.
MOVE SPACES TO WS-OUT-LINE.
STRING WS-FIELD1 DELIMITED BY SPACES
'|' WS-FIELD2 DELIMITED BY SPACES
'|' WS-SEGMENT(WS-SEG-IDX) DELIMITED BY SPACES
INTO WS-OUT-LINE
END-STRING.
MOVE WS-OUT-LINE TO OUT-REC.
WRITE OUT-REC.
IF WS-FILE-OUT-STATUS NOT = '00'
PERFORM 9900-TIMESTAMP
DISPLAY '[' WS-TIMESTAMP '] '
'ERROR: WRITE FILE-OUT status: '
WS-FILE-OUT-STATUS
MOVE WS-LINE-COUNT TO WS-ERR-LINE-NUM
MOVE WS-SEV-E TO WS-ERR-SEVERITY
STRING 'WRITE FAIL status=' WS-FILE-OUT-STATUS
INTO WS-ERR-DESC
ADD 1 TO WS-ERR-SEV-COUNT WS-ERR-TOTAL
PERFORM 6000-ERROR-HANDLE
END-IF.
ADD 1 TO WS-REC-COUNT WS-TOTAL-OUTPUT WS-SEG-TOTAL.
COMPUTE WS-HASH-FIELD-LEN =
FUNCTION LENGTH(FUNCTION TRIM(WS-SEGMENT(WS-SEG-IDX))).
ADD WS-HASH-FIELD-LEN TO WS-HASH-TOTAL.
3300-EXIT. EXIT.
*> ============================================================
*> 4000-REPORT SECTION — batch totals & reconciliation
*> ============================================================
4000-REPORT SECTION.
4000-START.
PERFORM 9900-TIMESTAMP.
DISPLAY '[' WS-TIMESTAMP '] '
'============================================'.
DISPLAY '[' WS-TIMESTAMP '] ' 'CsvFbLf BATCH REPORT'.
DISPLAY '[' WS-TIMESTAMP '] '
'============================================'.
DISPLAY '[' WS-TIMESTAMP '] '
'Input lines : ' WS-TOTAL-INPUT.
DISPLAY '[' WS-TIMESTAMP '] '
'Output records : ' WS-TOTAL-OUTPUT.
DISPLAY '[' WS-TIMESTAMP '] '
'Segments expanded : ' WS-SEG-TOTAL.
DISPLAY '[' WS-TIMESTAMP '] '
'Error rep entries : ' WS-ERR-REP-WRITTEN.
DISPLAY '[' WS-TIMESTAMP '] '
'--------------------------------------------'.
DISPLAY '[' WS-TIMESTAMP '] '
'Error total : ' WS-ERR-TOTAL.
DISPLAY '[' WS-TIMESTAMP '] '
' I=' WS-INFO-COUNT
' W=' WS-WARN-COUNT
' E=' WS-ERR-SEV-COUNT
' C=' WS-CRIT-COUNT.
DISPLAY '[' WS-TIMESTAMP '] '
'--------------------------------------------'.
DISPLAY '[' WS-TIMESTAMP '] '
'Malformed : ' WS-MALFORMED-CNT.
DISPLAY '[' WS-TIMESTAMP '] '
'Truncations: ' WS-TRUNC-CNT.
DISPLAY '[' WS-TIMESTAMP '] '
'Field errs : ' WS-FIELD-COUNT-ERR.
DISPLAY '[' WS-TIMESTAMP '] '
'Hash total : ' WS-HASH-TOTAL.
*> Final file status check
IF WS-FILE-IN-STATUS NOT = '00' AND NOT = '10'
DISPLAY ' FILE-IN final status: ' WS-FILE-IN-STATUS.
IF WS-FILE-OUT-STATUS NOT = '00' AND NOT = '10'
DISPLAY ' FILE-OUT final status: ' WS-FILE-OUT-STATUS.
IF WS-ERR-STATUS NOT = '00' AND NOT = '10'
DISPLAY ' ERROR-REPORT final status: ' WS-ERR-STATUS.
IF WS-AUDIT-STATUS NOT = '00' AND NOT = '10'
DISPLAY ' AUDIT-FILE final status: ' WS-AUDIT-STATUS.
*> Record count reconciliation
DISPLAY '--------------------------------------------'.
DISPLAY 'RECONCILIATION'.
IF WS-TOTAL-INPUT >= WS-MALFORMED-CNT
COMPUTE WS-EXPECTED-OUT =
WS-TOTAL-INPUT - WS-MALFORMED-CNT
ELSE MOVE ZERO TO WS-EXPECTED-OUT.
IF WS-TOTAL-OUTPUT = WS-EXPECTED-OUT
MOVE 'M' TO WS-RECON-STATUS
DISPLAY 'MATCH: input=' WS-TOTAL-INPUT
' output=' WS-TOTAL-OUTPUT
ELSE
COMPUTE WS-RECON-DIFF =
WS-TOTAL-OUTPUT - WS-EXPECTED-OUT
MOVE 'X' TO WS-RECON-STATUS
DISPLAY 'MISMATCH: input=' WS-TOTAL-INPUT
' output=' WS-TOTAL-OUTPUT
' diff=' WS-RECON-DIFF
END-IF.
DISPLAY '============================================'.
4000-EXIT. EXIT.
*> ============================================================
*> 5000-AUDIT SECTION
*> ============================================================
5000-AUDIT SECTION.
5000-START.
PERFORM 9900-TIMESTAMP.
MOVE SPACES TO AUD-REC.
STRING '=== CSV LF Import Summary === '
WS-CUR-DATE ' ' WS-CUR-TIME
INTO AUD-REC.
WRITE AUD-REC.
IF WS-AUDIT-STATUS NOT = '00'
DISPLAY 'AUDIT header WRITE status: ' WS-AUDIT-STATUS.
MOVE SPACES TO AUD-REC. WRITE AUD-REC.
MOVE WS-TOTAL-INPUT TO WS-RPT-COUNT.
STRING 'Input lines read: ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-TOTAL-OUTPUT TO WS-RPT-COUNT.
STRING 'Output records written: ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-SEG-TOTAL TO WS-RPT-COUNT.
STRING 'Segments expanded: ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
*> Error summary
MOVE SPACES TO AUD-REC. WRITE AUD-REC.
STRING 'Error summary —' INTO AUD-REC. WRITE AUD-REC.
MOVE WS-ERR-TOTAL TO WS-RPT-COUNT.
STRING ' Total: ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-INFO-COUNT TO WS-RPT-COUNT.
STRING ' INFO (I): ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-WARN-COUNT TO WS-RPT-COUNT.
STRING ' WARN (W): ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-ERR-SEV-COUNT TO WS-RPT-COUNT.
STRING ' ERROR (E): ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-CRIT-COUNT TO WS-RPT-COUNT.
STRING ' CRIT (C): ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
*> Breakdown
MOVE SPACES TO AUD-REC. WRITE AUD-REC.
STRING 'Malformed records: ' WS-MALFORMED-CNT
INTO AUD-REC. WRITE AUD-REC.
STRING 'Truncation warnings: ' WS-TRUNC-CNT
INTO AUD-REC. WRITE AUD-REC.
STRING 'Field count errors: ' WS-FIELD-COUNT-ERR
INTO AUD-REC. WRITE AUD-REC.
*> Reconciliation
MOVE SPACES TO AUD-REC. WRITE AUD-REC.
STRING 'Reconciliation: ' WS-RECON-STATUS
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-EXPECTED-OUT TO WS-RPT-COUNT.
STRING ' Expected output: ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-TOTAL-OUTPUT TO WS-RPT-COUNT.
STRING ' Actual output: ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
IF WS-RECON-MISMATCH
MOVE WS-RECON-DIFF TO WS-RPT-COUNT
STRING ' Difference: ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
*> Batch control totals
MOVE SPACES TO AUD-REC. WRITE AUD-REC.
STRING '=== Batch Control Totals ==='
INTO AUD-REC. WRITE AUD-REC.
STRING 'Batch date: ' WS-CUR-DATE
INTO AUD-REC. WRITE AUD-REC.
STRING 'Batch time: ' WS-CUR-TIME
INTO AUD-REC. WRITE AUD-REC.
MOVE WS-HASH-TOTAL TO WS-RPT-COUNT.
STRING 'Hash total (seg lens): ' WS-RPT-COUNT
INTO AUD-REC. WRITE AUD-REC.
MOVE SPACES TO AUD-REC. WRITE AUD-REC.
STRING '=== End of Audit Report ==='
INTO AUD-REC. WRITE AUD-REC.
5000-EXIT. EXIT.
*> ============================================================
*> 6000-ERROR-HANDLE SECTION
*> ============================================================
6000-ERROR-HANDLE SECTION.
6000-START.
MOVE SPACES TO ERR-REC.
STRING WS-ERR-LINE-NUM ' '
WS-ERR-SEVERITY ' '
WS-ERR-DESC
INTO ERR-REC.
WRITE ERR-REC.
IF WS-ERR-STATUS NOT = '00'
DISPLAY 'ERROR: WRITE ERROR-REPORT status: '
WS-ERR-STATUS.
ADD 1 TO WS-ERR-REP-WRITTEN.
DISPLAY '[' WS-TIMESTAMP '] '
'ERR: line=' WS-ERR-LINE-NUM
' sev=' WS-ERR-SEVERITY
' ' WS-ERR-DESC.
6000-EXIT. EXIT.
*> ============================================================
*> 9000-EXIT SECTION
*> ============================================================
9000-EXIT SECTION.
9000-START.
PERFORM 9900-TIMESTAMP.
*> Close ERROR-REPORT
IF WS-ERR-REP-IS-OPEN
MOVE SPACES TO ERR-REC WRITE ERR-REC
MOVE '--- End of Error Report ---' TO ERR-REC
WRITE ERR-REC
CLOSE ERROR-REPORT
IF WS-ERR-STATUS NOT = '00'
DISPLAY 'ERROR: CLOSE ERROR-REPORT status: '
WS-ERR-STATUS
ELSE
DISPLAY 'ERROR-REPORT closed' END-IF.
*> Close FILE-OUT
IF WS-FILE-OUT-IS-OPEN
CLOSE FILE-OUT
IF WS-FILE-OUT-STATUS NOT = '00'
DISPLAY 'ERROR: CLOSE FILE-OUT status: '
WS-FILE-OUT-STATUS
ELSE DISPLAY 'FILE-OUT closed' END-IF.
*> Close AUDIT-FILE
IF WS-AUDIT-IS-OPEN
CLOSE AUDIT-FILE
IF WS-AUDIT-STATUS NOT = '00'
DISPLAY 'ERROR: CLOSE AUDIT-FILE status: '
WS-AUDIT-STATUS
ELSE DISPLAY 'AUDIT-FILE closed' END-IF.
*> Close FILE-IN
IF WS-FILE-IN-IS-OPEN
CLOSE FILE-IN
IF WS-FILE-IN-STATUS NOT = '00'
DISPLAY 'ERROR: CLOSE FILE-IN status: '
WS-FILE-IN-STATUS
ELSE DISPLAY 'FILE-IN closed' END-IF.
*> Final summary
DISPLAY '[' WS-TIMESTAMP '] '
'CsvFbLf COMPLETED.'
' Input=' WS-LINE-COUNT
' Output=' WS-REC-COUNT
' Errors=' WS-ERR-TOTAL.
DISPLAY '[' WS-TIMESTAMP '] '
' Malformed=' WS-MALFORMED-CNT
' Truncated=' WS-TRUNC-CNT
' FieldErr=' WS-FIELD-COUNT-ERR.
DISPLAY '[' WS-TIMESTAMP '] '
' Hash=' WS-HASH-TOTAL
' ErrRep=csv-lf-errors.dat'
' Audit=csv-lf-audit.dat'.
STOP RUN.
9000-EXIT-POINT. EXIT.
*> ============================================================
*> 9900-TIMESTAMP SECTION
*> ============================================================
9900-TIMESTAMP SECTION.
9900-START.
MOVE FUNCTION CURRENT-DATE (1:8) TO WS-CUR-DATE.
MOVE FUNCTION CURRENT-DATE (9:6) TO WS-CUR-TIME.
MOVE WS-CUR-DATE TO WS-DISP-DATE.
MOVE WS-CUR-TIME TO WS-DISP-TIME.
MOVE WS-DISP-TS TO WS-TIMESTAMP.
9900-EXIT. EXIT.
END PROGRAM CsvFbLf.