✓ parity-verified

Live Preview

Enter stdin input for the program and click Run.


View modern code
#!/usr/bin/env python3
"""ACCT-STMT — deposit-account monthly statement calculator.

Idiomatic Python 3 rebuild of a free-format COBOL program.
Reads three whole-number values from stdin (one per line):
  1) account type   1=Checking  2=Savings  3=Money Market
  2) opening balance (whole dollars, unsigned)
  3) transaction count this cycle
Writes a formatted statement to stdout.

Decimal is used for currency arithmetic to mirror COBOL fixed-point
rounding (ROUND_HALF_UP) and PIC-style edited output formatting.
"""

import sys
from decimal import Decimal, ROUND_HALF_UP


def read_int(stream):
    """ACCEPT a numeric line. COBOL ACCEPT into an unsigned PIC 9 field
    interprets the digits; we read one line and parse as int."""
    line = stream.readline()
    return int(line.strip())


def fmt_pic_bal(value):
    """PIC Z,ZZZ,ZZ9 — 7 digits, comma grouping, leading-zero suppression
    (one mandatory final 9 so zero prints as '0'). Field width 9."""
    # Truncate to PIC 9(7): keep low 7 digits.
    n = int(value) % 10_000_000
    s = format(n, ",d")
    return s.rjust(9)


def fmt_pic_int(value):
    """PIC ZZ,ZZ9.99 — 5 integer digits w/ grouping (one mandatory 9),
    2 decimal places. Field width 9."""
    q = value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    int_part = int(q)
    frac = (q - int_part).copy_abs().quantize(Decimal("0.01"))
    int_str = format(int_part, ",d")
    frac_str = format(frac, ".2f")[2:]  # two digits after the point
    return (int_str + "." + frac_str).rjust(9)


def fmt_pic_fee(value):
    """PIC ZZ9.99 — 3 integer digits (one mandatory 9), 2 decimals.
    Field width 6."""
    q = value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    int_part = int(q)
    frac = (q - int_part).copy_abs().quantize(Decimal("0.01"))
    frac_str = format(frac, ".2f")[2:]
    return (str(int_part) + "." + frac_str).rjust(6)


def fmt_pic_end(value):
    """PIC -Z,ZZZ,ZZ9.99 — leading floating minus sign, 7 integer digits
    with grouping (one mandatory 9), 2 decimals. Field width 13.

    COBOL edits the sign into the suppressed-zero area: a single '-' floats
    immediately left of the most significant printed digit, with the rest
    of the suppression zone blank-filled to the left.
    """
    q = value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    negative = q < 0
    mag = q.copy_abs()
    int_part = int(mag)
    frac = (mag - int_part).quantize(Decimal("0.01"))
    int_str = format(int_part, ",d")
    frac_str = format(frac, ".2f")[2:]
    body = int_str + "." + frac_str
    # Width 13: optional sign char then the edited number, right-justified.
    if negative:
        # Sign floats just left of the number; pad the rest with spaces.
        out = "-" + body
        return out.rjust(13)
    return body.rjust(13)


def checking_rules(balance, txns):
    rate = Decimal("0")
    if balance >= 1500 or txns >= 15:
        fee = Decimal("0")
    else:
        fee = Decimal("12.00")
    return rate, fee


def savings_rules(balance, txns):
    if balance >= 10000:
        rate = Decimal("0.0100")
    else:
        rate = Decimal("0.0050")
    if balance >= 300:
        fee = Decimal("0")
    else:
        fee = Decimal("5.00")
    return rate, fee


def mm_rules(balance, txns):
    if balance >= 25000:
        rate = Decimal("0.0225")
    else:
        rate = Decimal("0.0150")
    if balance >= 10000:
        fee = Decimal("0")
    else:
        fee = Decimal("15.00")
    return rate, fee


def main():
    acct_type = read_int(sys.stdin)
    balance = read_int(sys.stdin)
    txn_count = read_int(sys.stdin)

    # COBOL fixed-field truncation on ACCEPT:
    #   WS-ACCT-TYPE PIC 9  -> 1 digit
    #   WS-BALANCE   PIC 9(7)
    #   WS-TXN-COUNT PIC 9(3)
    acct_type %= 10
    balance %= 10_000_000
    txn_count %= 1000

    if acct_type == 1:
        type_name = "CHECKING"
        rate, fee = checking_rules(balance, txn_count)
    elif acct_type == 2:
        type_name = "SAVINGS"
        rate, fee = savings_rules(balance, txn_count)
    elif acct_type == 3:
        type_name = "MONEY MARKET"
        rate, fee = mm_rules(balance, txn_count)
    else:
        type_name = "UNKNOWN"
        rate = Decimal("0")
        fee = Decimal("0")

    # COMPUTE WS-INTEREST ROUNDED = WS-BALANCE * WS-ANNUAL-RATE / 12
    # WS-INTEREST is PIC 9(7)V99 -> 2 decimal places, ROUNDED = half-up.
    interest = (Decimal(balance) * rate / Decimal(12)).quantize(
        Decimal("0.01"), rounding=ROUND_HALF_UP
    )

    # COMPUTE WS-ENDING = WS-BALANCE + WS-INTEREST - WS-FEE  (no ROUNDED)
    ending = Decimal(balance) + interest - fee

    if ending < 0:
        od_fee = Decimal("35.00")
        ending = ending - od_fee
    else:
        od_fee = Decimal("0")

    # WS-TYPE-NAME is PIC X(12): left-justified, space-padded to 12.
    type_field = type_name.ljust(12)

    out = sys.stdout
    out.write("ACCOUNT TYPE : " + type_field + "\n")
    out.write("OPENING BAL  : " + fmt_pic_bal(balance) + "\n")
    out.write("INTEREST     : " + fmt_pic_int(interest) + "\n")
    out.write("MAINT FEE    : " + fmt_pic_fee(fee) + "\n")
    out.write("OVERDRAFT FEE: " + fmt_pic_fee(od_fee) + "\n")
    out.write("ENDING BAL   : " + fmt_pic_end(ending) + "\n")


if __name__ == "__main__":
    main()