From 9778d308621c6e72681c13657b807696a8ef2013 Mon Sep 17 00:00:00 2001 From: Ivan Kudryavtsev Date: Thu, 25 Dec 2025 10:13:51 -0800 Subject: [PATCH] add task --- .../task-cobol-mortgage/Dockerfile | 11 + .../task-cobol-mortgage/data/OUTPUT.DAT | 1 + .../task-cobol-mortgage/docker-compose.yaml | 12 + .../task-cobol-mortgage/run-tests.sh | 2 + .../task-cobol-mortgage/solution.sh | 213 ++++++++++++++++++ .../task-cobol-mortgage/src/INPUT.DAT | 2 + .../task-cobol-mortgage/task.yaml | 34 +++ .../task-cobol-mortgage/tests/test_outputs.py | 82 +++++++ 8 files changed, 357 insertions(+) create mode 100644 contributor_tasks/task-cobol-mortgage/Dockerfile create mode 100644 contributor_tasks/task-cobol-mortgage/data/OUTPUT.DAT create mode 100644 contributor_tasks/task-cobol-mortgage/docker-compose.yaml create mode 100644 contributor_tasks/task-cobol-mortgage/run-tests.sh create mode 100755 contributor_tasks/task-cobol-mortgage/solution.sh create mode 100644 contributor_tasks/task-cobol-mortgage/src/INPUT.DAT create mode 100644 contributor_tasks/task-cobol-mortgage/task.yaml create mode 100644 contributor_tasks/task-cobol-mortgage/tests/test_outputs.py diff --git a/contributor_tasks/task-cobol-mortgage/Dockerfile b/contributor_tasks/task-cobol-mortgage/Dockerfile new file mode 100644 index 0000000..a71daff --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/Dockerfile @@ -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/* \ No newline at end of file diff --git a/contributor_tasks/task-cobol-mortgage/data/OUTPUT.DAT b/contributor_tasks/task-cobol-mortgage/data/OUTPUT.DAT new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/data/OUTPUT.DAT @@ -0,0 +1 @@ + diff --git a/contributor_tasks/task-cobol-mortgage/docker-compose.yaml b/contributor_tasks/task-cobol-mortgage/docker-compose.yaml new file mode 100644 index 0000000..e0ef70a --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/docker-compose.yaml @@ -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} diff --git a/contributor_tasks/task-cobol-mortgage/run-tests.sh b/contributor_tasks/task-cobol-mortgage/run-tests.sh new file mode 100644 index 0000000..30dbb4b --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/run-tests.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pytest $TEST_DIR/test_outputs.py -v -rA \ No newline at end of file diff --git a/contributor_tasks/task-cobol-mortgage/solution.sh b/contributor_tasks/task-cobol-mortgage/solution.sh new file mode 100755 index 0000000..2a089e0 --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/solution.sh @@ -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 diff --git a/contributor_tasks/task-cobol-mortgage/src/INPUT.DAT b/contributor_tasks/task-cobol-mortgage/src/INPUT.DAT new file mode 100644 index 0000000..7559c6d --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/src/INPUT.DAT @@ -0,0 +1,2 @@ +APP0000001;Williams David Robert ;2000000000;3500000000;0500000000;20;00550 +APP0000002;John Smith Clark ;0000005000;0000004000;0000003500;01;00550 \ No newline at end of file diff --git a/contributor_tasks/task-cobol-mortgage/task.yaml b/contributor_tasks/task-cobol-mortgage/task.yaml new file mode 100644 index 0000000..d9fba58 --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/task.yaml @@ -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 diff --git a/contributor_tasks/task-cobol-mortgage/tests/test_outputs.py b/contributor_tasks/task-cobol-mortgage/tests/test_outputs.py new file mode 100644 index 0000000..0e6d6ae --- /dev/null +++ b/contributor_tasks/task-cobol-mortgage/tests/test_outputs.py @@ -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" + )