import pandas import random from tabulate import tabulate from datetime import date import math # Functions def make_statement(statement, decoration): """Emphasises headings by adding decoration at the start and end""" return f"{decoration * 3} {statement} {decoration * 3}" def yes_no(question): """Checks for yes/y or no/n responses""" while True: response = input(question).strip().lower() if response in ("yes", "y"): return "yes" elif response in ("no", "n"): return "no" else: print("Please enter yes (y) or no (n).\n") def string_check(question, valid_answers, num_letters=1): """Checks for valid string responses""" while True: response = input(question).lower().strip() for valid_item in valid_answers: if response == valid_item or response == valid_item[:num_letters]: return valid_item print(f"Please choose an option from {valid_answers}.") def not_blank(question): """Checks that response is not blank""" while True: response = input(question).strip() if response: return response print("Sorry, this can't be blank. Please try again.\n") def num_check(question, num_type="float", exit_code=None): """Checks for valid number input""" error = "Please enter a number more than 0." if num_type == "float" else "Please enter an integer more than 0." while True: response = input(question) if exit_code and response.lower() == exit_code: return response try: response = float(response) if num_type == "float" else int(response) if response > 0: return response print(error) except ValueError: print(error) def currency(x): """Formats numbers as currency""" return "${:.2f}".format(x) def get_expenses(exp_type, how_many=1): """Gets expenses and returns formatted table and subtotal""" all_items, all_amounts, all_dollar_per_item = [], [], [] expenses_dict = {"Item": all_items, "Amount": all_amounts, "$ / Item": all_dollar_per_item} while True: item_name = not_blank("Item Name (or 'xxx' to stop): ") if exp_type == "variable" and item_name.lower() == "xxx" and not all_items: print("Oops - you must enter at least one item.") continue elif item_name.lower() == "xxx": break if exp_type == "variable": amount = num_check(f"How many: ", "integer", exit_code="") amount = how_many if amount == "" else int(amount) else: amount = 1 price_for_one = num_check("Price for one? $", "float") all_items.append(item_name) all_amounts.append(amount) all_dollar_per_item.append(price_for_one) expense_frame = pandas.DataFrame(expenses_dict) expense_frame['Cost'] = expense_frame['Amount'] * expense_frame['$ / Item'] subtotal = expense_frame['Cost'].sum() for column in ['Amount', '$ / Item', 'Cost']: expense_frame[column] = expense_frame[column].apply(currency) expense_string = tabulate(expense_frame, headers='keys', tablefmt='psql', showindex=False) if exp_type == "variable" else tabulate(expense_frame[['Item', 'Cost']], headers='keys', tablefmt='psql', showindex=False) return expense_string, subtotal def profit_goal(total_costs): """Calculates profit goal""" error = "Please enter a valid profit goal\n" while True: response = input("What is your profit goal (eg $500 or 50%): ").strip() if response.startswith("$"): profit_type = "$" amount = response[1:] elif response.endswith("%"): profit_type = "%" amount = response[:-1] else: profit_type = "unknown" amount = response try: amount = float(amount) if amount <= 0: print(error) continue except ValueError: print(error) continue if profit_type == "unknown" and amount >= 100: profit_type = "$" if yes_no(f"Do you mean ${amount:.2f}? ") == "yes" else "%" elif profit_type == "unknown" and amount < 100: profit_type = "%" if yes_no(f"Do you mean {amount:.0f}%? ") == "yes" else "$" return amount if profit_type == "$" else (amount / 100) * total_costs def round_up(amount, round_val): """Rounds amount up to nearest specified value""" return int(math.ceil(amount / round_val)) * round_val # Main Routine CREDIT_SURCHARGE = 0.05 tickets_sold = 0 all_names, all_ticket_costs, all_surcharges = [], [], [] ticket_dict = {'Name': all_names, 'Ticket Price': all_ticket_costs, 'Surcharge': all_surcharges} payment_ans = ('cash', 'credit') # Program Intro print(make_statement("Aquarium Ticket Fundraiser", "🐠")) print() if yes_no("Do you want to see the instructions? ") == "yes": print(make_statement("Instructions", "â„šī¸")) print(''' Welcome to the Aquarium Ticket System 🐠 Here's how it works: 1. Enter a event name for the Aquarium (e.g., "Summer Fish Fest") and how many tickets are available. 2. Add costs for things that depend on ticket numbers, like fish food or staff wages. You'll need at least one cost. 3. Add fixed costs, like tank cleaning, if you have any. (you can skip these) 4. Set a profit goal, either as a dollar amount (e.g., $500) or a percentage of costs (e.g., 50%). 5. Choose how to round ticket prices (e.g., to the nearest $1 or 50c) for simple pricing. 6. For each ticket, enter: - The buyer's name - Their age (12 to 120 years) - Payment method (cash or credit; credit has a 5% extra fee) 7. Type 'xxx' when done selling tickets or when you reach your ticket limit. 8. The program picks a random raffle winner who gets their ticket free! 9. You'll see a summary of all costs, ticket sales, and profits, saved to a file with the event name and date. Type 'xxx' to stop entering costs or tickets at any time. ''') else: print("Instructions skipped.") # Get Expenses print() product_name = not_blank("Aquarium Event Name: ") quantity = num_check("Number of tickets available: ", "integer") print("\nEnter variable expenses (e.g., fish food, staff costs)...") variable_expenses = get_expenses("variable", quantity) variable_panda_string, variable_subtotal = variable_expenses print() has_fixed = yes_no("Do you have fixed expenses (e.g., tank maintenance)? ") fixed_panda_string, fixed_subtotal = get_expenses("fixed") if has_fixed == "yes" else ("", 0) total_expenses = variable_subtotal + fixed_subtotal print() target_profit = profit_goal(total_expenses) sales_needed = total_expenses + target_profit round_to = num_check("Round ticket prices to nearest (e.g., 1 for $1, 0.5 for 50c)? ", "float") unrounded_price = sales_needed / quantity recommended_price = round_up(unrounded_price, round_to) # Calculate and Store Ticket Prices ticket_prices = { "child": round(recommended_price * 2 / 3, 2), # Children (<16) get 2/3 of adult price "adult": recommended_price, # Adults (16–64) use recommended price "senior": round(recommended_price * 1 / 2, 2) # Seniors (65+) get 1/2 of adult price } # Display Expense Summary and Ticket Prices print() print(make_statement("Event Cost Summary", "💰")) print(f"{'Total Expenses':<20}: {currency(total_expenses)}") print(f"{'Profit Goal':<20}: {currency(target_profit)}") print(f"{'Ticket Prices':<20}:") print(f"{' Child (<16)':<20}: {currency(ticket_prices['child'])}") print(f"{' Adult (16–64)':<20}: {currency(ticket_prices['adult'])}") print(f"{' Senior (65+)':<20}: {currency(ticket_prices['senior'])}") print("These prices will be used for ticket sales to meet your profit goal.") # Ticket Sales Loop print() while tickets_sold < quantity: print(f"\n--- Ticket {tickets_sold + 1} of {quantity} ---") name = not_blank("Name: ") if name.lower() == "xxx": break age = num_check("Age: ", "integer") if age < 12: print(f"{name} is too young.") continue elif age > 120: print(f"{name} is too old.") continue ticket_price = ticket_prices["child"] if age < 16 else ticket_prices["senior"] if age >= 65 else ticket_prices[ "adult"] pay_method = string_check("Payment method (cash/credit): ", payment_ans, 2) surcharge = 0 if pay_method == "cash" else ticket_price * CREDIT_SURCHARGE all_names.append(name) all_ticket_costs.append(ticket_price) all_surcharges.append(surcharge) tickets_sold += 1 print(f"{name} has bought a ticket ({pay_method}) for {currency(ticket_price + surcharge)}") # Summary ticket_frame = pandas.DataFrame(ticket_dict) ticket_frame['Total'] = ticket_frame['Ticket Price'] + ticket_frame['Surcharge'] ticket_frame['Profit'] = ticket_frame['Ticket Price'] - (total_expenses / quantity) total_paid = ticket_frame['Total'].sum() total_profit = ticket_frame['Profit'].sum() winner = random.choice(all_names) if all_names else "No entries" winner_index = all_names.index(winner) if all_names else None ticket_won = ticket_frame.at[winner_index, 'Total'] if winner_index is not None else 0 profit_won = ticket_frame.at[winner_index, 'Profit'] if winner_index is not None else 0 for field in ['Ticket Price', 'Surcharge', 'Total', 'Profit']: ticket_frame[field] = ticket_frame[field].apply(currency) # Output Strings today = date.today() file_name = f"{product_name}_{today.strftime('%Y_%m_%d')}.txt" to_write = [ make_statement(f"Aquarium Fundraiser - {product_name}", "="), f"\nNumber of tickets: {quantity}", make_statement("Variable Expenses", "-"), variable_panda_string, f"Variable Subtotal: ${variable_subtotal:.2f}", make_statement("Fixed Expenses", "-") if has_fixed == "yes" else make_statement("No Fixed Expenses", "-"), fixed_panda_string, f"Fixed Subtotal: ${fixed_subtotal:.2f}", f"\nTotal Expenses: ${total_expenses:.2f}", f"\nProfit Goal: ${target_profit:.2f}", f"Total Sales Needed: ${sales_needed:.2f}", f"Unrounded Ticket Price: ${unrounded_price:.2f}", f"Recommended Ticket Price (Adult): ${recommended_price:.2f}", f"Child Ticket Price (<16): ${ticket_prices['child']:.2f}", f"Senior Ticket Price (65+): ${ticket_prices['senior']:.2f}", make_statement("Ticket Sales", "-"), ticket_frame.to_string(index=False), f"\nTotal Paid: ${total_paid:.2f}", f"Total Profit: ${total_profit:.2f}", make_statement("Raffle Winner", "-"), f"Lucky Winner: {winner}. Their ticket worth ${ticket_won:.2f} is free!" if winner != "No entries" else "No raffle due to no ticket sales.", f"Adjusted Total Paid: ${total_paid - ticket_won:.2f}", f"Adjusted Total Profit: ${total_profit - profit_won:.2f}", make_statement(f"Sold {tickets_sold}/{quantity} tickets", "-") if tickets_sold < quantity else make_statement( f"All {quantity} tickets sold!", "-") ] # Print and Write to File print() for item in to_write: print(item) with open(file_name, "w") as text_file: for item in to_write: text_file.write(str(item) + "\n")