BillBook

Database project designed for processing large amount of bills

Build for Simplification

This project is a debt optimization and tracking system designed to simplify financial reconciliation among groups. At its core, the system allows users to record events that involve shared expenses, specifying the event name, payer, participants, and bill amount. From this data, the program calculates and tracks debts for each individual, automatically determining how much each participant owes or is owed. The system provides comprehensive functionality for parsing, storing, and displaying events, along with detailed debt records, making it a practical tool for managing shared finances.

// Sample code showing CSV support and general logic
let rec process_rec_list
    (recolist : record list) (original : record list) : user list =
  match recolist with
  | [] ->
      []
  | reco :: hd ->
      (* print_endline ("thisprint" ^ record_to_string reco) ; *)
      process_one_debter reco recolist original
      :: process_rec_list hd original


(*Genearl Idea: Given a list of events, return a list of users such
  that the debt list of each user is optimized. 1. transform the list
  of events into users 2. sort the users in terms of debt 3. reimburse
  the user that is owed the most with the user that owes the least,
  and then with the user that owes the second least, etc. *)
let optimizer (e : event list) : user list =
  let debt_record : record list = [] in
  (*STEP 1*)
  let record2 = create_record e debt_record in
  (*STEP 2*)
  let sorted_record = record_lst_sort record2 in
  (* print_endline (recordlist_to_string sorted_record) ; *)
  (*STEP 3. use list.rev because the sorting sequence is from least to
    most*)
  let result =
    process_rec_list (List.rev sorted_record) sorted_record
  in
  (* print_endline (userlist_to_string result) ; *)
  result
// Sample code of processing data for one user
let process_one_debter
    (reco : record) (recolist : record list) (original : record list)
    : user =
  let this_debt = check_in_debt reco original in
  if this_debt <= 0.
  then
    if this_debt *. this_debt <= 0.00000000001 (*WE NEED TO CLIP*)
    then { name = reco.name; debt = []; total_debt = 0. }
    else { name = reco.name; debt = []; total_debt = this_debt }
  else
    let debt2 = ref [] in
    let current_debt = ref reco.debt in
    let _ =
      while !current_debt <> 0. do
        let first_index = find_first recolist in
        (* print_endline (string_of_int first_index) ; print_endline
           (recordlist_to_string recolist) ; *)
        if first_index = List.length recolist
        then current_debt := 0.
        else
          let first_record = List.nth recolist first_index in
          if first_record.debt +. !current_debt <= 0.
          then (
            if !current_debt < 0.0000000001
            then current_debt := 0.
            else
              let new_debt = (first_record.name, !current_debt) in
              debt2 := new_debt :: !debt2 ;
              first_record.debt <- first_record.debt +. !current_debt ;
              current_debt := 0. )
          else if first_record.debt *. first_record.debt
                  < 0.0000000001
          then first_record.debt <- 0.
          else
            let new_debt =
              (first_record.name, -1. *. first_record.debt)
            in
            debt2 := new_debt :: !debt2 ;
            current_debt := first_record.debt +. !current_debt ;
            first_record.debt <- 0.
      done
    in
    if this_debt *. this_debt <= 0.0000000001
    then
      let result =
        { name = reco.name; debt = !debt2; total_debt = 0. }
      in
      result
    else
      let result =
        { name = reco.name; debt = !debt2; total_debt = this_debt }
      in
      (* print_endline (user_to_string result); *)
      result

Optimization with Algorithm

The standout feature of the project lies in its debt optimization algorithm. This algorithm transforms a list of events into an optimized list of debts, minimizing unnecessary transactions among participants. By sorting debts and iteratively balancing the largest creditors and debtors, the program ensures that reimbursements are efficient and equitable. This sophisticated approach not only reduces the complexity of settling debts but also makes the system robust and scalable for larger groups with numerous transactions.

Convenient UI

print_endline "" ;
print_endline "Welcome to BillBook!" ;
print_endline "" ;
print_endline "To input an event, type in 'record'. " ;
print_endline "To acquire information of a user, type in 'mydebt'." ;
print_endline "To acquire information of an event, type in 'check'." ;
print_endline
  "For more functionalities or further info for these functions, \
   enter 'help'. " ;
print_endline "To quit, type 'quit'."

open Functions

let events = read_csv_file "test.csv"

let updated = ref false

let usrlst : user list ref = ref []

let rec action first_input evts updt lst =
  match first_input with
  | [ "quit" ] ->
      exit 0
  | "summary" :: _ ->
      print_endline "The following events have been recorded: " ;
      List.iter see_event evts
  | "record" :: _ ->
      print_endline "Enter the name of the event: " ;
      let a = String.trim (input_line stdin) in
      print_endline "Enter the name of the payer: " ;
      let b = String.trim (input_line stdin) in
      print_endline
        "Enter the names of the participants, seperated by ',': " ;
      let c =
        String.split_on_char ',' (String.trim (input_line stdin))
      in
      print_endline "Enter the bill amount: " ;
      let d = float_of_string (String.trim (input_line stdin)) in
      let evt = input_event a b c d in
      let evt_lst = [ evt ] in
      see_event evt ;
      updt := false ;
      write_csv_file "test.csv" (List.append evts evt_lst)
  | "check" :: _ ->
      print_endline "Enter the name of the event: " ;
      let event_name = String.trim (input_line stdin) in
      let matching_events =
        List.filter (fun evt -> evt.event_name = event_name) evts
      in
      if List.length matching_events > 0
      then List.iter see_event matching_events
      else print_endline "Event name not found."
  | "delete" :: _ ->
      print_endline "Enter the name of the event: " ;
      let event_name = String.trim (input_line stdin) in
      let remaining_events =
        List.filter (fun evt -> evt.event_name <> event_name) evts
      in
      write_csv_file "test.csv" remaining_events ;
      updt := false ;
      print_endline "The event has been deleted."
  | "debt" :: _ ->
      if !updt = false
      then (
        print_endline "Debts summarized as follows: " ;
        updt := true ;
        let user_list = optimizer evts in
        lst := user_list ;
        print_endline (userlist_to_string user_list) )
      else print_endline (userlist_to_string !lst)
  | "mydebt" :: _ ->
      print_endline "Enter the name of the user: " ;
      let user_name = String.trim (input_line stdin) in
      let user_list =
        if !updt = false
        then (
          updt := true ;
          lst := optimizer evts ;
          !lst )
        else !lst
      in
      let user =
        List.find_opt (fun user -> user.name = user_name) user_list
      in
      ( match user with
      | Some u ->
          print_endline (user_to_string u)
      | None ->
          print_endline ("No user named " ^ user_name ^ " found.") )
  | [ "help" ] ->
      print_endline (help_function "")
  | "help" :: fun_name :: _ ->
      print_endline (help_function fun_name)
  | _ ->
      print_endline
        "Unknown command. To quit the program enter 'quit'. Enter \
         'help' for more info." ;
      let second_input =
        String.split_on_char ' ' (String.trim (input_line stdin))
      in
      action second_input evts updt lst


let () =
  let rec wait_for_command () =
    let input =
      String.split_on_char ' ' (String.trim (input_line stdin))
    in
    action input events updated usrlst ;
    wait_for_command ()
  in
  wait_for_command ()

The main application code connects all components through a user-friendly command-line interface (CLI). It loads event data from a CSV file and allows users to record new events, check existing ones, summarize debts, and view individual debt records. By updating the CSV file after each change and invoking the debt optimization algorithm as needed, the app ensures accurate and efficient management of shared expenses in real-time.