packages = ['pandas', 'numpy']
Patch 1.4 18/09/2022:
- New feature: Float Attendant Reservation Report:
- Input a reservations.csv for specific date range to separate and identify bookings and actual revenue made/received per F.A.
- Can be further altered as an excel file to determine percentage/ratio of revenue compared to total sales for date range and
in comparison with bookings that didn't receive upselling/prepayment.
- What this fixes:
- Floathelm can't show you a report of bookings made by all employees. Without this functionality you would need to generate 'n'
amount of csv files and patch them all together. While the algorithm does use slight hardcoding to prioritise staff made bookings,
this is a lot faster than cut and pasting all the files together.
- What can still be fixed:
- As it stands, there are a couple of prepaid massage bookings that make it into the reservation report unintentionally.
- This is because for whatever reason we have over 50 different items for our massages that all serve the exact same purpose: Delivering
the exact same cost per massage.
- This is concurrent with the odd occurence of a particular massage on a schedule not being coloured dark blue despite having a valid
massage credit on the user's account.
- Upcoming Features:
- No upcoming functionalities thus far. However the website will be refactored to generate multiple composite reports with a single input
as opposed to multiple redundant inputs of a particular file that is shared between functions
- For Example:
- Inputting a sales.csv will generate:
- Non-Native Gift Card Report
- Float Attendant Upgrade Leaderboard
- Inputting a customers.csv will generate:
- Duplicate Customer Report
- Inputting a reservations.csv will generate:
- Float Attendant Reservation Report
- Point being; functionalities that share alike inputs will be generated at the same time.
Patch 1.3 17/09/2022
- New Feature: Float Attendant Upgrade Leaderboard
-
Input a sales.csv for specific date range to separate and identify specific Float Attendant's success in upselling particular packages,
specifically the packages requiring more intervention and customer service to make. Intro Packs, 20 Packs etc.
- Can be further added to, to show how much $ value particular Float Attendant's make with particular packages and their ability to upsell
as a whole.
- What this fixes:
-
Floathelm is unable to provide a quick analysis of how many of a particular package has been sold. While you can specify a data range on
package sales for a particular store, you are still required to manually hand count how many of each package has been sold.
-
But floathelm can count how many of each package has been sold? Correct, but not as in-depth as we can. With the incredibly frequent
and efficient use of the upgrade component, floathelm gets confused and doesn't quite understand when a combination of two item
sales creates an intro pack, especially when the closing of the two components are done on different days.
- An incredibly quick reference to understand how close you are to outselling a coworker and vice versa.
- Upcoming Features:
- The Float Attendant Reservation Report:
- Whilst upselling is very much the fun of sales on the front. The majority of the montlhy sales come from the money made from the base booking itself.
- The purpose of this functionality will be to accurately determine who was responsible for 'x' amount of revenue for the month in comparison with another.
Patch 1.2 15/09/2022
- New Features: Duplicate Customer Report, Non-Native Gift Card Report
- Duplicate Customer Report:
- Input a customers.csv file to generate a list of duplicate customers within the system, including a vast number of reasons as to their creation:
- Float Attendants not taking emails
- Customers forgetting passwords and rather than resetting using a whole new email
- Float Attendants misspelling emails
- Parent-Child Relationship
- What this fixes:
- Doing this in juxt with the Customer Merge Tool in Floathelm will allow centres to clean their data and ultimately prevent
customer complaints in the event that they have service credits that are unable to be found because they were bought on a different account.
- This also allows averages to become more true as the average follows as 'Average = Total Sales/Total Customers,'
allowing for better numbers and recovered $ in lost averages.
- Cleaning our data with this tool will allow accuracy across all of Floathelm and Strong Wellness toolkit.
- Non-Native Gift Card Report
- Input a sales.csv to generate a list of all gift cards used outside of a given set of stores, what they were for, where they came from
and how much they were worth.
- Allows stores to see potential losses in the balance between booking numbers and revenue for the services themselves.
- What this fixes:
- Floathelm doesn't have this particular functionality within arms reach, at least 10 different actions must be performed to gain minor insight
- Upcoming Features:
- The Float Attendant Upgrade Leaderboard is coming back!
- What once was an incredibly ambitious project to gain access to the floathelm backend has now been resurrected for the sake of monthly/weekly reporting.
import asyncio
from js import document, FileReader
from pyodide.ffi import create_proxy
import pandas as pd
import numpy as np
import io
async def generate_duplicate_customers(event):
#READ AND PROCESS PROXY FILE
file = event.target.files.to_py()
data=""
for f in file:
data = await f.text()
csv_buffer = io.StringIO(data)
#FILTER FOR BAD CUSTOMERS
df = pd.read_csv(csv_buffer)
df['FullName'] = df['Customer First Name'] + " " + df['Customer Last Name']
dup_num = df.groupby('Phone').filter(lambda x: len(x) >= 2)
ready = dup_num.loc[:,['Phone', 'FullName', 'Email']]
out = ready.groupby('Phone').value_counts().to_csv()
#APPEND DOWNLOADABLE CSV
element = document.createElement('a')
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + out)
element.innerHTML = "Duplicate Customers Report"
document.getElementById("dup-cust-output").appendChild(element)
async def generate_external_giftcards(event):
#READ AND PROCESS PROXY FILE
file = event.target.files.to_py()
data=""
for f in file:
data = await f.text()
csv_buffer = io.StringIO(data)
#FILTER FOR EXTERNAL BOUGHT GIFTCARDS
df = pd.read_csv(csv_buffer)
test = df[['id', 'State', 'Customer Name', 'Items', 'Total']]
drop = test.dropna()
gc_sales = drop[drop['Items'].str.contains('for Gift Card')]
remove_MN = gc_sales[gc_sales['Items'].str.contains('nmin') == False]
remove_BV = remove_MN[remove_MN['Items'].str.contains('nbel') == False]
remove_RH = remove_BV[remove_BV['Items'].str.contains('nrou') == False]
final = remove_RH.to_csv()
#APPEND DOWNLOADABLE CSV
element = document.createElement('a')
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + final)
element.innerHTML = "Non-Native GiftCard Report"
document.getElementById("external-giftcard-output").appendChild(element)
async def generate_upgrade_tally(event):
#READ AND PROCESS PROXY FILE
file = event.target.files.to_py()
data=""
for f in file:
data = await f.text()
csv_buffer = io.StringIO(data)
#FILTER FOR PACKAGE SALES & ATTENDANTS
df = pd.read_csv(csv_buffer)
findUpgrades(df)
df = excludeNormals(df)
imp = df[['Items', 'Closed By']]
count = imp.groupby('Closed By').value_counts()
tally = pd.DataFrame(data=count)
output_tally = tally.to_csv()
#APPEND DOWNLOADABLE CSV
element = document.createElement('a')
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + output_tally)
element.innerHTML = "FA Upgrade Tally"
document.getElementById("Upgrade-Tally-Output").appendChild(element)
async def generate_booking_revenue(event):
#READ AND PROCESS PROXY FILE
file = event.target.files.to_py()
data=""
for f in file:
data = await f.text()
csv_buffer = io.StringIO(data)
#FILTER FOR NON-PREPAID BOOKINGS PER FA
df = pd.read_csv(csv_buffer)
excludeCustomers(df)
imp = df[['Service', 'Booked By', 'Service Credit Source', 'Customer First Name', 'Customer Last Name', 'Total Paid']]
remove_prepay = imp.loc[imp['Service Credit Source'] == 'individual']
count = remove_prepay.groupby('Booked By').value_counts()
out = count.to_csv()
#APPEND DOWNLOADABLE CSV
element = document.createElement('a')
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + out)
element.innerHTML = "FA Reservation Report"
document.getElementById("booking-revenue-output").appendChild(element)
async def first_timer(event):
#READ AND PROCESS PROXY FILE
file = event.target.files.to_py()
data=""
for f in file:
data = await f.text()
csv_buffer = io.StringIO(data)
#FIND APPTS. THAT WERE UPGRADEABLE
df = pd.read_csv(csv_buffer)
df = df[['Service','Customer First Name', 'Customer Last Name', 'Customer Appointment Number','Total Paid',
'Checkout By']]
df = findUpgradable(df)
df = df.loc[df['Customer Appointment Number'] == 1]
df = df[['Checkout By', 'Service']]
df = df.groupby('Checkout By').value_counts()
new = pd.DataFrame(data=df)
outputVal = new.to_csv()
#APPEND DOWNLOADABLE CSV
element = document.createElement('a')
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + outputVal)
element.innerHTML = "First Timer Report"
document.getElementById("First-Timer-Output").appendChild(element)
async def gc_raffle(event):
#READ AND PROCESS PROXY FILE
file = event.target.files.to_py()
data=""
for f in file:
data = await f.text()
csv_buffer = io.StringIO(data)
sales = pd.read_csv(csv_buffer)
sales = sales[["Date", "Items", "Total"]]
sales = sales.dropna()
sales = sales[sales['Items'].str.contains('Gift Card')]
sales['Total'] = sales['Total'].str.replace('$','', regex=False)
sales["Total"] = pd.to_numeric(sales["Total"])
sales = sales[sales["Total"] > 0]
sales["Tickets"] = np.floor(sales["Total"].astype(float) / 50)
sales = sales.reset_index(drop=True)
sales["TicketNumbers"] = 0
running = 0
for i in range(len(sales)):
if(i == 0):
sales.at[i, "TicketNumbers"] = sales.at[i, "Tickets"]
running += sales.at[i, "Tickets"]
else:
running += sales.at[i-1, "Tickets"]
sales.at[i, "TicketNumbers"] = " " + str(running.astype(int)) + " - " + str((running.astype(int)) + int(sales.at[i, "Tickets"] - 1))
sales = sales[["Date", "Items", "Total", "Tickets", "TicketNumbers"]]
outputVal = sales.to_csv()
#APPEND DOWNLOADABLE CSV
element = document.createElement('a')
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + outputVal)
element.innerHTML = "Raffle Report"
document.getElementById("gc-raffle-Output").appendChild(element)
def findUpgradable(df):
values = df['Total Paid']
for i in range(len(values)):
if(values[i] == '$79.00'):
continue
if(values[i] == '$94.00'):
continue
if(values[i] == '$44.00'):
continue
if(values[i] == '$59.00'):
continue
if(values[i] == '$90.00'):
continue
if(values[i] == '$110.00'):
continue
if(values[i] == '$150.00'):
continue
df = df.drop(i)
return df
def findUpgrades(df):
items = df['Items']
for i in range(len(items)):
#Sauna
if('Upgrade to Couples Infrared Sauna Intro Pack' in items[i]):
df.loc[i,['Items']] = 'Couples Sauna Intro Pack'
continue
if('Couples Sauna Intro pack' in items[i]):
df.loc[i,['Items']] = 'Couples Sauna Intro Pack'
continue
if('Infrared Sauna Intro pack upgrade' in items[i]):
df.loc[i,['Items']] = 'Sauna Intro Pack'
continue
if('Sauna Intro Pack' in items[i]):
df.loc[i,['Items']] = 'Sauna Intro Pack'
continue
if('1 x SAUNA (CAVE CLUB) membership' in items[i]):
df.loc[i,['Items']] = 'Sauna Membership'
continue
if('Sauna 20 Pack' in items[i]):
df.loc[i,['Items']] = 'Sauna 20 Pack'
continue
#Float
if('Couples Intro Float Pack' in items[i]):
df.loc[i,['Items']] = 'Couples Float Intro Pack'
continue
if('Upgrade to Couples Float Intro Pack' in items[i]):
df.loc[i,['Items']] = 'Couples Float Intro Pack'
continue
if('Float Starter Intro Pack' in items[i]):
df.loc[i,['Items']] = 'Float Intro Pack'
continue
if('Float Therapy intro pack upgrade' in items[i]):
df.loc[i,['Items']] = 'Float Intro Pack'
continue
if('1 x FLOAT (CAVE CLUB) membership' in items[i]):
df.loc[i,['Items']] = 'Float Membership'
continue
if('Float Therapy 24 Pack' in items[i]):
df.loc[i,['Items']] = 'Float 24 Pack'
continue
#Massage
if('Massage Intro Pack' in items[i]):
df.loc[i,['Items']] = 'Massage Intro Pack'
continue
if('Massage-Intro pack upgrade' in items[i]):
df.loc[i,['Items']] = 'Massage Intro Pack'
continue
if('1 x MASSAGE CAVE CLUB' in items[i]):
df.loc[i,['Items']] = 'Massage Membership'
def excludeNormals(df):
items = df['Items']
for i in range(len(items)):
if(items[i] == 'Float Intro Pack'):
continue
if(items[i] == 'Couples Float Intro Pack'):
continue
if(items[i] == 'Float Membership'):
continue
if(items[i] == 'Float 24 Pack'):
continue
if(items[i] == 'Sauna Intro Pack'):
continue
if(items[i] == 'Couples Sauna Intro Pack'):
continue
if(items[i] == 'Sauna Membership'):
continue
if(items[i] == 'Sauna 20 Pack'):
continue
if(items[i] == 'Massage Intro Pack'):
continue
if(items[i] == 'Massage Membership'):
continue
df = df.drop(i)
return df
def excludeCustomers(df):
atts = df['Booked By']
for i in range(len(atts)):
if('Cassie Berry 🍓' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Cassie Berry 🍓'
continue
if('Charlize Schoeman🧜' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Charlize Schoeman🧜'
continue
if('Elaria Tadros' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Elaria Tadros'
continue
if('Jacinta Galea 👸' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Jacinta Galea 👸'
continue
if('Jeremy Woodward🏃♂️' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Jeremy Woodward🏃♂️'
continue
if('Lucas Rose🥏' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Lucas Rose🥏'
continue
if('Nat Dolly🥳' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Nat Dolly🥳'
continue
if('Stella Halatanu' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Stella Halatanu'
continue
if('Tanya Motiani 🌟' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Tanya Motiani 🌟'
continue
if('Thomas Hogan 🎧' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Thomas Hogan 🎧'
continue
if('Wisdom Agbeze🧙' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Wisdom Agbeze🧙'
continue
if('Jaime Strong' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Jaime Strong'
continue
if('Kira Schembri 🍋' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Kira Schembri 🍋'
continue
if('Katelyn Woods 🍑' in atts[i]):
df.loc[i,['Booked By']] = 'Katelyn Woods 🍑'
continue
if('Judah Raposo Alves🦄' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Judah Raposo Alves🦄'
continue
if('Andrew Vecchio🤸🏻♂️' in atts[i]):
df.loc[i,['Booked By']] = 'AAA Andrew Vecchio🤸🏻♂️'
continue
def dupCustProxy():
# Create a Python proxy for the callback function
# generate_duplicate_customers() is your function to process events from FileReader
file_event = create_proxy(generate_duplicate_customers)
# Set the listener to the callback
e = document.getElementById("dupCust")
e.addEventListener("change", file_event, False)
def extGCProxy():
# generate_external_giftcards() is your function to process events from FileReader
file_event = create_proxy(generate_external_giftcards)
e = document.getElementById("badGC")
e.addEventListener("change", file_event, False)
def upgradeLBProxy():
file_event = create_proxy(generate_upgrade_tally)
e = document.getElementById("upgradeTally")
e.addEventListener("change", file_event, False)
def bookingRevenueProxy():
file_event = create_proxy(generate_booking_revenue)
e = document.getElementById("bookingRevenue")
e.addEventListener("change", file_event, False)
def firstTimerProxy():
file_event = create_proxy(first_timer)
e = document.getElementById("firstTimer")
e.addEventListener("change", file_event, False)
def raffleProxy():
file_event = create_proxy(gc_raffle)
e = document.getElementById("gc-raffle")
e.addEventListener("change", file_event, False)
dupCustProxy()
extGCProxy()
upgradeLBProxy()
bookingRevenueProxy()
firstTimerProxy()
raffleProxy()