From a1cdd69c42e70a5ad514a381156029bd41011a8c Mon Sep 17 00:00:00 2001 From: scuti Date: Fri, 30 Aug 2024 01:49:12 -0700 Subject: [PATCH] merge branch publish --- Makefile | 2 +- README.md | 45 +++++++++++++++++++++++++++++++ amort.py => src/amort.py | 57 +++++++++++++++++++++------------------- 3 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 README.md rename amort.py => src/amort.py (82%) diff --git a/Makefile b/Makefile index 2d851c9..4ac9153 100644 --- a/Makefile +++ b/Makefile @@ -3,5 +3,5 @@ reference: amortize -P 100000 -r 0.05 -f monthly -n 36 -s > ref.txt test: - python amort.py -p 100000 -i 5.0 -t 3 -ot "{\"payment_number\":13,\"amount\":5000}" + python src/amort.py -p 100000 -i 5.0 -t 3 -ot "{\"payment-number\":13,\"amount\":5000}" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad85776 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ + +# amort + +Generates an amortization schedule based on the principal, interest rate, term, and any additional payments towards principal. + +## requirements + +Only imports `argparse` and `json` for the main block of the script. + +* saves results to a file `schedule.csv` +* does not access the web or internet + +## usage + +Invoke `make test` to generate a schedule based on loan of 100,000 over a 3 year term given an interest rate of 5.0% with a one-time payment towards principal of 5000 at the 13th month. + +### extra payments + +For extra payments towards principal, invoking the script will look like: + + python src/amort.py -p {principal} -i {interest rate} -t {years} --extra-payments extra_payments.json + +A valid json file contains a list named `extra-payments`. Each element is an object with the attributes `payment-number` and `amount`. + + { + "extra-payments" : [ + { + "payment-number": 1, + "amount": 500 + }, { + "payment-number": 2, + "amount": 500 + }, { + "payment-number": 3, + "amount": 500 + } + ] + } + +* `payment-number` is the month when the payment has been made. +* `amount` is self-explanatory + +## misc + +This originated from me playing with ChatGPT. I asked it a question and it blurted out a semi-functional script as an answer. Then I made this repo to track the errors I fixed. diff --git a/amort.py b/src/amort.py similarity index 82% rename from amort.py rename to src/amort.py index 09498e0..b12b99a 100644 --- a/amort.py +++ b/src/amort.py @@ -1,6 +1,4 @@ -import argparse, json - def generate_amortization_schedule(principal, interest_rate, loan_term, extra_payments=[]): # Convert interest rate to decimal and calculate periodic interest rate monthly_interest_rate = interest_rate / 12 / 100 @@ -29,7 +27,7 @@ def generate_amortization_schedule(principal, interest_rate, loan_term, extra_pa principal_payment = round(monthly_payment - interest_payment, 2) # Apply one-time payment if provided - if one_time_payment and payment_number == one_time_payment['payment_number']: + if one_time_payment and payment_number == one_time_payment['payment-number']: principal_payment += one_time_payment['amount'] if extra_payments != []: one_time_payment = extra_payments.pop(0) @@ -72,7 +70,30 @@ def get_totals(amortization_schedule, func=None): func(messages) return total_paid, total_interest_paid, total_principal_paid +def compare(with_extra_payments, without): + x,y,z = get_totals(with_extra_payments) + a,b,c = get_totals(without) + term_length = len(without) - len(with_extra_payments) + print(chr(916), "paid: ", round(x-a,2)) + print(chr(916), "interest paid: ", round(y-b,2)) + print(chr(916), "principal paid: ", round(abs(z-c),2)) + print(chr(916), "term (months): ", term_length) + +def display(table): + print("id, paid, interest payment, principal payment, remaining") + for row in table: + print(row) + +def export(table, filename="schedule.csv"): + with open(filename, 'w') as f: + print("id, paid, interest payment, principal payment, remaining", file=f) + for row in table: + print(row, file=f) + print("wrote to file", filename) + if __name__ == "__main__": + import argparse, json + def get_arguments(): p = argparse.ArgumentParser() p. add_argument("--principal", "-p", type=float, \ @@ -82,7 +103,7 @@ if __name__ == "__main__": p.add_argument("--term", "-t", type=int,\ help="sets the term (years)") p.add_argument("--one-time", "-ot", type=str,\ - help="factors in a one-time payment (json, example: {\"payment_number\":13,\"amount\":5000}") + help="factors in a one-time payment (json, example: {\"payment-number\":13,\"amount\":5000}") p.add_argument("--extra-payments", "-ep", type=str,\ help="facts in multiple one time payments (json file name)") args = p.parse_args() @@ -90,32 +111,14 @@ if __name__ == "__main__": if args.extra_payments is not None: with open(args.extra_payments) as f: l = json.loads(f.read()) - # print(extra["extra-payments"]) + extra = [] + if "extra-payments" in l: + extra = l["extra-payments"] + extra.sort(key=lambda k: k["payment-number"]) if args.one_time is not None: - l.append(json.loads(args.one_time)) - extra = l["extra-payments"] - extra.sort(key=lambda k: k["payment_number"]) + extra.append(json.loads(args.one_time)) return args.principal, args.interest_rate, args.term, extra - def compare(with_extra_payments, without): - x,y,z = get_totals(with_extra_payments) - a,b,c = get_totals(without) - print(chr(916), "paid: ", round(x-a,2)) - print(chr(916), "interest paid: ", round(y-b,2)) - print(chr(916), "principal paid: ", round(abs(z-c),2)) - - def display(table): - print("id, paid, interest payment, principal payment, remaining") - for row in table: - print(row) - - def export(table, filename="schedule.csv"): - with open(filename, 'w') as f: - print("id, paid, interest payment, principal payment, remaining", file=f) - for row in table: - print(row, file=f) - print("wrote to file", filename) - principal, interest_rate, loan_term, extra_payments = get_arguments() schedule = generate_amortization_schedule(principal, interest_rate, loan_term, extra_payments)