Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions contributor_tasks/task-cobol-mortgage/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM ghcr.io/laude-institute/t-bench/python-3-13:20250620

WORKDIR /app

COPY src/ /app/src/

COPY data/ /app/data/

RUN pip install pytest==8.4.2

RUN apt-get update && apt-get install -y gnucobol3 && rm -rf /var/lib/apt/lists/*
1 change: 1 addition & 0 deletions contributor_tasks/task-cobol-mortgage/data/OUTPUT.DAT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

12 changes: 12 additions & 0 deletions contributor_tasks/task-cobol-mortgage/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
client:
build:
dockerfile: Dockerfile
image: ${T_BENCH_TASK_DOCKER_CLIENT_IMAGE_NAME}
container_name: ${T_BENCH_TASK_DOCKER_CLIENT_CONTAINER_NAME}
command: [ "sh", "-c", "sleep infinity" ]
environment:
- TEST_DIR=${T_BENCH_TEST_DIR}
volumes:
- ${T_BENCH_TASK_LOGS_PATH}:${T_BENCH_CONTAINER_LOGS_PATH}
- ${T_BENCH_TASK_AGENT_LOGS_PATH}:${T_BENCH_CONTAINER_AGENT_LOGS_PATH}
2 changes: 2 additions & 0 deletions contributor_tasks/task-cobol-mortgage/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
pytest $TEST_DIR/test_outputs.py -v -rA
213 changes: 213 additions & 0 deletions contributor_tasks/task-cobol-mortgage/solution.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#!/bin/bash

# Copy the provided COBOL code to the source file
cat > src/program.cbl << 'EOF'
IDENTIFICATION DIVISION.
PROGRAM-ID. MORTGAGE01.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INPUT-FILE ASSIGN TO 'src/INPUT.DAT'
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-INPUT-STATUS.
SELECT OUTPUT-FILE ASSIGN TO 'data/OUTPUT.DAT'
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-OUTPUT-STATUS.
DATA DIVISION.
FILE SECTION.
FD INPUT-FILE.
01 INPUT-REC.
05 IR-APPLICATION-ID PIC X(10).
05 FILLER PIC X(1).
05 IR-CUSTOMER-NAME PIC X(30).
05 FILLER PIC X(1).
05 IR-LOAN-AMOUNT PIC 9(10).
05 FILLER PIC X(1).
05 IR-PROPERTY-VALUE PIC 9(10).
05 FILLER PIC X(1).
05 IR-DOWN-PAYMENT PIC 9(10).
05 FILLER PIC X(1).
05 IR-LOAN-TERM PIC 9(2).
05 FILLER PIC X(1).
05 IR-INTEREST-RATE PIC 9(3)V9(2).
FD OUTPUT-FILE.
01 OUTPUT-REC PIC X(80).
WORKING-STORAGE SECTION.
77 WS-INPUT-STATUS PIC XX.
77 WS-OUTPUT-STATUS PIC XX.
77 WS-EOF-FLAG PIC X VALUE 'N'.
88 WS-EOF VALUE 'Y'.
88 WS-NOT-EOF VALUE 'N'.
77 WS-ERROR-FLAG PIC X VALUE 'N'.
88 WS-ERROR VALUE 'Y'.
88 WS-NO-ERROR VALUE 'N'.
77 WS-MIN-DOWN-PAYMENT PIC 9(10).
77 WS-MONTHLY-RATE PIC 9(3)V9(6).
77 WS-TOTAL-MONTHS PIC 9(4).
77 WS-ANNUITY-FACTOR PIC 9(4)V9(9).
77 WS-MONTHLY-PAYMENT PIC 9(8)V9(2).
77 WS-REMAINING-BALANCE PIC 9(10)V9(2).
77 WS-INTEREST-PART PIC 9(8)V9(2).
77 WS-PRINCIPAL-PART PIC 9(8)V9(2).
77 WS-MONTH-COUNTER PIC 999.
77 WS-TEMP-VALUE PIC 9(10)V9(9).
01 WS-OUTPUT-REC.
05 FILLER PIC X(80).
01 WS-DECLINE-REC.
05 DR-APPLICATION-ID PIC X(10).
05 DR-STATUS PIC X(5).
05 DR-DECLINE-REASON PIC X(50).
05 DR-FILLER PIC X(15).
01 WS-APPROVE-HEADER.
05 AH-APPLICATION-ID PIC X(10).
05 AH-STATUS PIC X(5).
05 AH-FILLER PIC X(65).
01 WS-SCHEDULE-REC.
05 SR-MONTH-NUMBER PIC 9(3).
05 FILLER PIC X(1).
05 SR-PAYMENT-AMOUNT PIC 9(8).99.
05 FILLER PIC X(1).
05 SR-INTEREST-PART PIC 9(8).99.
05 FILLER PIC X(1).
05 SR-PRINCIPAL-PART PIC 9(8).99.
05 FILLER PIC X(1).
05 SR-REMAINING-BALANCE PIC 9(10).99.
05 SR-FILLER PIC X(27).
PROCEDURE DIVISION.
MAIN-PROCEDURE.
PERFORM OPEN-FILES THRU OPEN-FILES-EX
IF WS-NO-ERROR
PERFORM PROCESS-FILE THRU PROCESS-FILE-EX
UNTIL WS-EOF OR WS-ERROR
END-IF
PERFORM CLOSE-FILES THRU CLOSE-FILES-EX
STOP RUN.
OPEN-FILES.
OPEN INPUT INPUT-FILE
IF WS-INPUT-STATUS NOT = '00'
MOVE 'Y' TO WS-ERROR-FLAG
END-IF
IF WS-NO-ERROR
OPEN OUTPUT OUTPUT-FILE
IF WS-OUTPUT-STATUS NOT = '00'
MOVE 'Y' TO WS-ERROR-FLAG
END-IF
END-IF
IF WS-ERROR
ELSE
MOVE 'N' TO WS-EOF-FLAG
END-IF.
OPEN-FILES-EX.
EXIT.
PROCESS-FILE.
READ INPUT-FILE
AT END
MOVE 'Y' TO WS-EOF-FLAG
NOT AT END
PERFORM PROCESS-APPLICATION THRU PROCESS-APPLICATION-EX
END-READ.
PROCESS-FILE-EX.
EXIT.
PROCESS-APPLICATION.
COMPUTE WS-MIN-DOWN-PAYMENT =
IR-PROPERTY-VALUE * 0.15
EVALUATE TRUE
WHEN IR-DOWN-PAYMENT < WS-MIN-DOWN-PAYMENT
PERFORM CREATE-DECLINE THRU CREATE-DECLINE-EX
WHEN IR-DOWN-PAYMENT >= WS-MIN-DOWN-PAYMENT
PERFORM CALCULATE-SCHEDULE THRU CALCULATE-SCHEDULE-EX
WHEN OTHER
MOVE 'DATA-ERR' TO DR-STATUS
PERFORM CREATE-DECLINE THRU CREATE-DECLINE-EX
END-EVALUATE.
PROCESS-APPLICATION-EX.
EXIT.
CREATE-DECLINE.
MOVE IR-APPLICATION-ID TO DR-APPLICATION-ID
MOVE 'DENY' TO DR-STATUS
MOVE 'INSUFFICIENT FUNDS FOR DOWN PAYMENT'
TO DR-DECLINE-REASON
MOVE SPACES TO DR-FILLER
MOVE WS-DECLINE-REC TO WS-OUTPUT-REC
WRITE OUTPUT-REC FROM WS-OUTPUT-REC
IF WS-OUTPUT-STATUS NOT = '00'
MOVE 'Y' TO WS-ERROR-FLAG
END-IF.
CREATE-DECLINE-EX.
EXIT.
CALCULATE-SCHEDULE.
COMPUTE WS-MONTHLY-RATE =
IR-INTEREST-RATE / 12 / 100
COMPUTE WS-TOTAL-MONTHS = IR-LOAN-TERM * 12
COMPUTE WS-TEMP-VALUE = (1 + WS-MONTHLY-RATE)
COMPUTE WS-TEMP-VALUE = WS-TEMP-VALUE ** WS-TOTAL-MONTHS
COMPUTE WS-MONTHLY-PAYMENT ROUNDED =
IR-LOAN-AMOUNT *
(WS-MONTHLY-RATE * WS-TEMP-VALUE) /
(WS-TEMP-VALUE - 1)
MOVE IR-APPLICATION-ID TO AH-APPLICATION-ID
MOVE 'APPRO' TO AH-STATUS
MOVE SPACES TO AH-FILLER
MOVE WS-APPROVE-HEADER TO WS-OUTPUT-REC
WRITE OUTPUT-REC FROM WS-OUTPUT-REC
IF WS-OUTPUT-STATUS NOT = '00'
MOVE 'Y' TO WS-ERROR-FLAG
END-IF
IF WS-NO-ERROR
MOVE IR-LOAN-AMOUNT TO WS-REMAINING-BALANCE
PERFORM CREATE-SCHEDULE-EN THRU CREATE-SCHEDULE-EN-EX
VARYING WS-MONTH-COUNTER FROM 1 BY 1
UNTIL WS-MONTH-COUNTER > WS-TOTAL-MONTHS
OR WS-ERROR
END-IF.
CALCULATE-SCHEDULE-EX.
EXIT.
CREATE-SCHEDULE-EN.
COMPUTE WS-INTEREST-PART ROUNDED =
WS-REMAINING-BALANCE * WS-MONTHLY-RATE
COMPUTE WS-PRINCIPAL-PART ROUNDED =
WS-MONTHLY-PAYMENT - WS-INTEREST-PART
COMPUTE WS-REMAINING-BALANCE =
WS-REMAINING-BALANCE - WS-PRINCIPAL-PART
IF WS-MONTH-COUNTER = WS-TOTAL-MONTHS
IF WS-REMAINING-BALANCE > 0.01 OR
WS-REMAINING-BALANCE < -0.01
COMPUTE WS-PRINCIPAL-PART =
WS-PRINCIPAL-PART + WS-REMAINING-BALANCE
COMPUTE WS-MONTHLY-PAYMENT =
WS-INTEREST-PART + WS-PRINCIPAL-PART
MOVE ZERO TO WS-REMAINING-BALANCE
ELSE
MOVE ZERO TO WS-REMAINING-BALANCE
END-IF
END-IF
MOVE WS-MONTH-COUNTER TO SR-MONTH-NUMBER
MOVE WS-MONTHLY-PAYMENT TO SR-PAYMENT-AMOUNT
MOVE WS-INTEREST-PART TO SR-INTEREST-PART
MOVE WS-PRINCIPAL-PART TO SR-PRINCIPAL-PART
MOVE WS-REMAINING-BALANCE TO SR-REMAINING-BALANCE
MOVE SPACES TO SR-FILLER
MOVE WS-SCHEDULE-REC TO WS-OUTPUT-REC
WRITE OUTPUT-REC FROM WS-OUTPUT-REC
IF WS-OUTPUT-STATUS NOT = '00'
MOVE 'Y' TO WS-ERROR-FLAG
END-IF.
CREATE-SCHEDULE-EN-EX.
EXIT.
CLOSE-FILES.
IF WS-INPUT-STATUS = '00' OR '05'
CLOSE INPUT-FILE
END-IF
IF WS-OUTPUT-STATUS = '00' OR '05'
CLOSE OUTPUT-FILE
END-IF.
CLOSE-FILES-EX.
EXIT.
EOF

cobc -x -o src/programCOMP src/program.cbl

# Run the COBOL program
./src/programCOMP

rm src/programCOMP
2 changes: 2 additions & 0 deletions contributor_tasks/task-cobol-mortgage/src/INPUT.DAT
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
APP0000001;Williams David Robert ;2000000000;3500000000;0500000000;20;00550
APP0000002;John Smith Clark ;0000005000;0000004000;0000003500;01;00550
34 changes: 34 additions & 0 deletions contributor_tasks/task-cobol-mortgage/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
instruction: |-
Your task is to create a file /app/src/program.cbl in which there will be a new COBOL program in fixed format,
which will be run from the /app/src directory and should be compiled and executed using GnuCOBOL 3.
Do not include ">>SOURCE FORMAT IS FIXED". Keep each line ≤ 80 characters. The compiler will use fixed format by default.
The program will run from /app/src directory and must compile with GnuCOBOL 3. File /app/src/INPUT.DAT already exist.
Processes mortgage applications. Checks terms and generates a payment schedule. The program reads the existing file at /app/src/INPUT.DAT.
ID - X(10) NAME - X(30) LOAN-AMOUNT - 9(10) PROPERTY-VALUE - 9(10) DOWN-PAYMENT - 9(10) TERM - 9(2) RATE - 9(3)V9(2) separated by the symbol ';'.
Calculates the minimum down payment (15% of the property value). If the client's down payment is less, the application is rejected.
For approved applications, the monthly payment is calculated using the annuity nominal formula and a full payment schedule is generated.
The results are written to the file at /app/data/OUTPUT.DAT: ID - X(10) STATUS - X(5) (REASON - X(50) in case of refusal)
For rejected applications: ID and reason for rejection.
For approved applications: ID and «APPRO\n» and detailed payment schedule:
MON-NUMBER - 9(3) PAYMENT-AMOUNT, INTEREST-PART, PRINCIPAL-PART - 9(8).99 REMAINING-BALANCE - 9(10).99.(without header).
- CALCULATIONS:
MIN_DOWN = PROP_VALUE * 0.15
MONTHLY_RATE = INTEREST_RATE / 1200
MONTHLY_PAYMENT = LOAN_AMOUNT * monthly_rate / (1 - (1 + monthly_rate)**(-term_months))
Use ROUNDED for all financial computations.
Success Criteria:
A correctly constructed payment schedule in the file output. Example output file:
«TEST000001DENY INSUFFICIENT FUNDS FOR DOWN PAYMENT
TEST000002APPRO
001 00000400.00 00000022.92 00000377.08 0000004622.92 etc.»

difficulty: hard
category: software-engineering
tags:
- coding
parser_name: pytest
max_agent_timeout_sec: 300.0
max_test_timeout_sec: 180.0
run_tests_in_same_shell: false
expert_time_estimate_min: 20
junior_time_estimate_min: 70
82 changes: 82 additions & 0 deletions contributor_tasks/task-cobol-mortgage/tests/test_outputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import subprocess
import os
from pathlib import Path


def test_required_files_exist():
"""Test that all required COBOL files exist"""
required_files = [
"/app/src/program.cbl",
]

for file_path in required_files:
assert Path(file_path).exists(), f"Required file program.cbl {file_path} does not exist"

def test_fixed_format_constraints():
"""Test that the code is written in fixed format"""
src = Path("/app/src/program.cbl").read_text()
assert ">>SOURCE FORMAT IS FIXED" not in src
longest = max(len(line.rstrip("\n")) for line in src.splitlines())
assert longest <= 80

def test_data_files_exist():
"""Test that all required data files exist"""
data_files = [
"/app/src/INPUT.DAT",
]

for file_path in data_files:
assert Path(file_path).exists(), f"Data file {file_path} does not exist"

def test_program_output():
"""Test that the program produces the expected output and file contents"""
initial_OUTPUT = ""

data_dir = Path("/app/data")

(data_dir / "OUTPUT.DAT").write_text(initial_OUTPUT)

Path("/app/src/INPUT.DAT").write_text(
"""APP0000001;Williams David Robert ;2000000000;3500000000;0500000000;20;00550
APP0000002;John Smith Clark ;0000005000;0000004000;0000003500;01;00550""")

# Compile the COBOL program
compile_result = subprocess.run([
'cobc',
'-x',
'-o',
'/app/src/programCOMP',
'/app/src/program.cbl'
], capture_output=True, text=True)

assert compile_result.returncode == 0, f"Compilation failed: {compile_result.stderr}"

# Run the compiled program
result = subprocess.run(
['/app/src/programCOMP'],
capture_output=True,
text=True
)

assert result.returncode == 0, f"Program failed: {result.stderr}"

expected_OUTPUT = (
"""APP0000001DENY INSUFFICIENT FUNDS FOR DOWN PAYMENT
APP0000002APPRO
001 00000429.18 00000022.92 00000406.26 0000004593.74
002 00000429.18 00000021.05 00000408.13 0000004185.61
003 00000429.18 00000019.18 00000410.00 0000003775.61
004 00000429.18 00000017.30 00000411.88 0000003363.73
005 00000429.18 00000015.42 00000413.76 0000002949.97
006 00000429.18 00000013.52 00000415.66 0000002534.31
007 00000429.18 00000011.61 00000417.57 0000002116.74
008 00000429.18 00000009.70 00000419.48 0000001697.26
009 00000429.18 00000007.78 00000421.40 0000001275.86
010 00000429.18 00000005.85 00000423.33 0000000852.53
011 00000429.18 00000003.91 00000425.27 0000000427.26
012 00000429.22 00000001.96 00000427.26 0000000000.00\n"""
)

assert (data_dir / "OUTPUT.DAT").read_text() == expected_OUTPUT, (
"OUTPUT.DAT content mismatch"
)