Skip to content

plkit #

A package for automating EUBC PlayerLayer kit orders

Modules:

  • generate_order_form

    Functions and classes for generating order forms given order information

  • read_orders

    Functions and classes for reading in order information from the excel sheet

  • validate

Functions:

read_order #

read_order(
    df_orders: DataFrame, name: str, email: str = None
)

Obtain the order information for a specific person

Parameters#

df_orders: pd.DataFrame The pandas DataFrame containing all the order information name : str The name of the person placing the order email : str, optional The email address of the person placing the order

Returns#

order : class Instance of the Order class for the specified name

Source code in plkit/read_orders.py
def read_order(df_orders: _pd.DataFrame, name: str, email: str = None):
    """
    Obtain the order information for a specific person

    Parameters
    ----------
    df_orders: pd.DataFrame
        The pandas DataFrame containing all the order information
    name : str
        The name of the person placing the order
    email : str, optional
        The email address of the person placing the order

    Returns
    -------
    order : class
        Instance of the Order class for the specified name
    """
    name = name.strip()

    # Check that names column exists
    if "Name" not in df_orders.columns:
        raise LookupError("Name column not found in input DataFrame")
    else:
        names = df_orders["Name"].to_list()

    # Check that email column exists
    if "Email" not in df_orders.columns:
        raise LookupError("Email column not found in input DataFrame")

    # Extract email if not specified
    if isinstance(email, str):
        email = email.strip()
    else:
        email = df_orders.loc[df_orders["Name"] == name, "Email"].iloc[0]

    name_count = names.count(name)

    if name_count == 0:
        raise LookupError(f"Name {name} not found!")
    elif name_count == 1:
        idx = df_orders[
            df_orders["Name"] == name
        ].index  # Only use email unless if are two identical names
    else:
        idx = df_orders[
            (df_orders["Name"] == name) & (df_orders["Email"] == email)
        ].index

    # Initialise class
    order_info = Order(
        email=email,
        name=name,
        items=_extract_items(df_orders, idx),
        sizings=_extract_sizings(df_orders, idx),
        back_names=_extract_back_names(df_orders, idx),
        sleeve_names=_extract_sleeve_names(df_orders, idx),
    )

    return order_info

extract_orders #

extract_orders(
    filename: str = "responses.xlsx",
) -> DataFrame

Read in the order response form as a pandas DataFrame.

Parameters#

filename : str, optional The name of the responses form saved from Microsoft forms

Returns#

df_orders : pd.DataFrame The order details converted to a pandas DataFrame

Source code in plkit/read_orders.py
def extract_orders(filename: str = "responses.xlsx") -> _pd.DataFrame:
    """
    Read in the order response form as a pandas DataFrame.

    Parameters
    ----------
    filename : str, optional
        The name of the responses form saved from Microsoft forms

    Returns
    -------
    df_orders : pd.DataFrame
        The order details converted to a pandas DataFrame
    """

    if not filename.endswith(".xlsx"):
        raise ValueError("Input must be an Excel File")

    if not _os.path.isfile(filename):
        raise FileNotFoundError(f"File {filename} does not exist")

    try:
        df_orders = _pd.read_excel(filename)
    except _pd.errors.EmptyDataError as e:
        raise _pd.errors.EmptyDataError(f"The file {filename} is empty") from e
    except Exception as e:
        raise Exception(f"An error occurred: {e}") from e

    # Clean hidden characters
    df_orders['Name'] = df_orders['Name'].apply(_clean_string)
    df_orders['Email'] = df_orders['Email'].apply(_clean_string)

    for number in ['First', 'Second', 'Third', 'Fourth', 'Fifth']:
        column_name = f'{number} kit item'
        df_orders[column_name] = df_orders[column_name].apply(_clean_string)
        column_name = f'{number} item - name personalisation for back (optional)'
        df_orders[column_name] = df_orders[column_name].apply(_clean_string)
        column_name = (f'{number} item - personalisation for initials (optional, '
        'max two letters)')
        df_orders[column_name] = df_orders[column_name].apply(_clean_string)

    for number in ['first', 'second', 'third', 'fourth', 'fifth']:
        column_name = (f"Sizing for {number} kit item (note that for women's tee, "
        "XS=size 6, S=size 8, ... , 4XL=20)"
        )
        df_orders[column_name] = df_orders[column_name].apply(_clean_string)

    return df_orders

generate_product_order #

generate_product_order(df_orders: DataFrame) -> DataFrame

Generate a DataFrame of product-specific order information, given a DataFrame of orders

Parameters#

df_orders: _pd.DataFrame The order details converted to a pandas DataFrame

Returns#

df_products: _pd.DataFrame Full order details for every product, completed with the orders contained in df_orders

Source code in plkit/generate_order_form.py
def generate_product_order(df_orders: _pd.DataFrame) -> _pd.DataFrame:
    """
    Generate a DataFrame of product-specific order information,
    given a DataFrame of orders

    Parameters
    ----------
    df_orders: _pd.DataFrame
        The order details converted to a pandas DataFrame

    Returns
    -------
    df_products: _pd.DataFrame
        Full order details for every product,
        completed with the orders contained in df_orders
    """

    # List of all products - women's sizing is different
    items = [
        "Unisex EcoLayer Hoodie",
        "Unisex EcoLayer Hoodie - 1 Personalisation",
        "Unisex EcoLayer Hoodie - 2 Personalisations",
        "Unisex Shield Performance Sweatshirt",
        "Unisex Shield Performance Sweatshirt - 1 Personalisation",
        "Unisex Shield Performance Sweatshirt - 2 Personalisations",
        "Men's EcoLayer Tee (Navy)",
        "Men's EcoLayer Tee - 1 Personalisation (Navy)",
        "Men's EcoLayer Tee - 2 Personalisations (Navy)",
        "Men's EcoLayer Tee (Forest)",
        "Men's EcoLayer Tee - 1 Personalisation (Forest)",
        "Men's EcoLayer Tee - 2 Personalisations (Forest)",
        "Women's EcoLayer Tee (Navy)",
        "Women's EcoLayer Tee - 1 Personalisation (Navy)",
        "Women's EcoLayer Tee - 2 Personalisations (Navy)",
        "Women's EcoLayer Tee (Forest)",
        "Women's EcoLayer Tee - 1 Personalisation (Forest)",
        "Women's EcoLayer Tee - 2 Personalisations (Forest)",
        "Men's Sublimated Tee (Navy)",
        "Men's Sublimated Tee - 1 Personalisation (Navy)",
        "Men's Sublimated Tee - 2 Personalisations (Navy)",
        "Men's Sublimated Tee (Forest)",
        "Men's Sublimated Tee - 1 Personalisation (Forest)",
        "Men's Sublimated Tee - 2 Personalisations (Forest)",
        "Women's Sublimated Tee (Navy)",
        "Women's Sublimated Tee - 1 Personalisation (Navy)",
        "Women's Sublimated Tee - 2 Personalisations (Navy)",
        "Women's Sublimated Tee (Forest)",
        "Women's Sublimated Tee - 1 Personalisation (Forest)",
        "Women's Sublimated Tee - 2 Personalisations (Forest)"
    ]

    # Names of all people who submitted an order
    names = df_orders["Name"].to_list()

    # Empty df to store product order info
    columns = [
        "Product Name",
        "Colour",
        "Total Quantity",
        "XS",
        "S",
        "M",
        "L",
        "XL",
        "2XL",
        "3XL",
        "4XL",
        "5XL",
        6,
        8,
        10,
        12,
        14,
        16,
        18,
        20,
        22,
        "Unit Price (£)",
        "Total Price (£)",
    ]
    df_products = _pd.DataFrame(columns=columns)

    for item in items:
        product = Product(item)  # Initialise empty class

        for name in names:  # Populate product with the info from all orders
            order = read_order(df_orders, name)
            order.identify_products()
            product.update_product(order)

        df_products = _update_df_products(df_products, product)

    # Add total pricing row
    count_all_items = _np.sum(df_products["Total Quantity"].to_numpy())
    total_price = _np.sum(df_products["Total Price (£)"].to_numpy())

    new_row = [_np.nan] * len(columns)
    new_row[2] = count_all_items
    new_row[-1] = total_price
    df_products.loc[len(df_products)] = new_row
    df_products.iloc[-1, -2] = "Total"  # Add label for total

    # Add label for club name
    new_row = [_np.nan] * len(columns)
    df_products.loc[len(df_products)] = new_row
    df_products.iloc[-1, 1] = "Club Name"
    df_products.iloc[-1, 2] = "Badminton"

    return df_products

generate_product_personalisations #

generate_product_personalisations(
    df_orders: DataFrame,
) -> DataFrame

Generate a DataFrame of product-specific order personalisations, given a DataFrame of orders

Parameters#

df_orders: _pd.DataFrame The order details converted to a pandas DataFrame

Returns#

df_personal: _pd.DataFrame Full personalisation details for every product, completed with the orders contained in df_orders

Source code in plkit/generate_order_form.py
def generate_product_personalisations(df_orders: _pd.DataFrame) -> _pd.DataFrame:
    """
    Generate a DataFrame of product-specific order personalisations,
    given a DataFrame of orders

    Parameters
    ----------
    df_orders: _pd.DataFrame
        The order details converted to a pandas DataFrame

    Returns
    -------
    df_personal: _pd.DataFrame
        Full personalisation details for every product,
        completed with the orders contained in df_orders
    """

    womens_sizing = {
        "XS": 6,
        "S": 8,
        "M": 10,
        "L": 12,
        "XL": 14,
        "2XL": 16,
        "3XL": 18,
        "4XL": 20,
        "5XL": 22,
    }

    # Names of all people who submitted an order
    names = df_orders["Name"].to_list()

    # Empty df to store product order info
    columns = [
        "Product Name",
        "Size",
        "Colour",
        "Initials (sleeve personalisation)",
        "Name (back personalisation)",
    ]

    df_personal = _pd.DataFrame(columns=columns)

    for name in names:  # Populate product with the info from all orders
        order = read_order(df_orders, name)
        order.identify_products()

        for n in range(len(order.items)):
            product_name = order.products[n]

            if isinstance(product_name, str):
                product_name = product_name.strip()
                sizing = order.sizings[n]

                new_row = {
                    "Product Name": product_name,
                    "Size": womens_sizing.get(sizing, sizing)
                    if "Women's" in product_name
                    else sizing,
                    "Colour": "Forest" if "Forest" in product_name else "Navy",
                    "Initials (sleeve personalisation)": order.sleeve_names[n],
                    "Name (back personalisation)": order.back_names[n],
                }

                # Only add column for personalised item
                if isinstance(order.sleeve_names[n], str) or isinstance(
                    order.back_names[n], str
                ):
                    df_personal = _pd.concat(
                        [df_personal, _pd.DataFrame([new_row])], ignore_index=True
                    )

    return df_personal

assert_order_count #

assert_order_count(
    df_orders: DataFrame, df_products: DataFrame
) -> None

Assert that the item count in the DataFrame of products to order matches the total number of items in the initial order.

Parameters#

df_orders: _pd.DataFrame The order details converted to a pandas DataFrame df_products: _pd.DataFrame Full order details for every product, completed with the orders contained in df_orders

Returns#

None

Source code in plkit/validate/tests.py
def assert_order_count(df_orders: _pd.DataFrame, df_products: _pd.DataFrame) -> None:
    """
    Assert that the item count in the DataFrame of
    products to order matches
    the total number of items in the initial order.

    Parameters
    ----------
    df_orders: _pd.DataFrame
        The order details converted to a pandas DataFrame
    df_products: _pd.DataFrame
        Full order details for every product,
        completed with the orders contained in df_orders

    Returns
    -------
    None
    """
    initial_count = int(count_initial_order(df_orders))
    processed_count = int(count_processed_order(df_products))

    assert initial_count == processed_count

assert_back_personalisations #

assert_back_personalisations(
    df_orders: DataFrame, df_personal: DataFrame
) -> None

Assert that the back name personalisations count in the DataFrame of products to order matches the total number of personalisations in the initial order.

Parameters#

df_orders: _pd.DataFrame The order details converted to a pandas DataFrame df_personal: __pd.DataFrame Full personalisation details for every product, completed with the orders contained in df_orders

Returns#

None

Source code in plkit/validate/tests.py
def assert_back_personalisations(
    df_orders: _pd.DataFrame, df_personal: _pd.DataFrame
) -> None:
    """
    Assert that the back name personalisations count in the
    DataFrame of products to order matches
    the total number of personalisations in the initial order.

    Parameters
    ----------
    df_orders: _pd.DataFrame
        The order details converted to a pandas DataFrame
    df_personal: __pd.DataFrame
        Full personalisation details for every product,
        completed with the orders contained in df_orders

    Returns
    -------
    None
    """
    initial_count = count_initial_back_personalisations(df_orders)
    processed_count = count_processed_back_personalisations(df_personal)

    assert initial_count == processed_count

assert_sleeve_personalisations #

assert_sleeve_personalisations(
    df_orders: DataFrame, df_personal: DataFrame
) -> None

Assert that the sleeve name personalisations count in the DataFrame of products to order matches the total number of personalisations in the initial order.

Parameters#

df_orders: _pd.DataFrame The order details converted to a pandas DataFrame df_personal: __pd.DataFrame Full personalisation details for every product, completed with the orders contained in df_orders

Returns#

None

Source code in plkit/validate/tests.py
def assert_sleeve_personalisations(
    df_orders: _pd.DataFrame, df_personal: _pd.DataFrame
) -> None:
    """
    Assert that the sleeve name personalisations count in the
    DataFrame of products to order matches
    the total number of personalisations in the initial order.

    Parameters
    ----------
    df_orders: _pd.DataFrame
        The order details converted to a pandas DataFrame
    df_personal: __pd.DataFrame
        Full personalisation details for every product,
        completed with the orders contained in df_orders

    Returns
    -------
    None
    """
    initial_count = count_initial_sleeve_personalisations(df_orders)
    processed_count = count_processed_sleeve_personalisations(df_personal)

    assert initial_count == processed_count

count_initial_order #

count_initial_order(df_orders: DataFrame) -> int

Internal function to count the total number of orders Some code is repeated in count_initial_back_personalisations and count_initial_sleeve_personalisations but cba to change it

Source code in plkit/validate/tests.py
def count_initial_order(df_orders: _pd.DataFrame) -> int:
    """Internal function to count the total number of orders
    Some code is repeated in count_initial_back_personalisations
    and count_initial_sleeve_personalisations but cba to change it"""
    item_count = 0

    # Check that names column exists
    if "Name" not in df_orders.columns:
        raise LookupError("Name column not found in input DataFrame")
    else:
        names = df_orders["Name"].to_list()

    for name in names:
        if isinstance(name, str):
            order = read_order(df_orders, name)

            for item in order.items:
                if isinstance(item, str):
                    item_count += 1

        else:
            raise ValueError(f"Non-string name {name} detected!")

    return int(item_count)

count_initial_back_personalisations #

count_initial_back_personalisations(
    df_orders: DataFrame,
) -> int

Internal function to count the total number of back name personalisations

Source code in plkit/validate/tests.py
def count_initial_back_personalisations(df_orders: _pd.DataFrame) -> int:
    """Internal function to count the total
    number of back name personalisations"""
    item_count = 0

    # Check that names column exists
    if "Name" not in df_orders.columns:
        raise LookupError("Name column not found in input DataFrame")
    else:
        names = df_orders["Name"].to_list()

    for name in names:
        if isinstance(name, str):
            order = read_order(df_orders, name)

            for item in order.back_names:
                if isinstance(item, str):
                    item_count += 1

        else:
            raise ValueError(f"Non-string name {name} detected!")

    return int(item_count)

count_initial_sleeve_personalisations #

count_initial_sleeve_personalisations(
    df_orders: DataFrame,
) -> int

Internal function to count the total number of orders

Source code in plkit/validate/tests.py
def count_initial_sleeve_personalisations(df_orders: _pd.DataFrame) -> int:
    """Internal function to count the total number of orders"""
    item_count = 0

    # Check that names column exists
    if "Name" not in df_orders.columns:
        raise LookupError("Name column not found in input DataFrame")
    else:
        names = df_orders["Name"].to_list()

    for name in names:
        if isinstance(name, str):
            order = read_order(df_orders, name)

            for item in order.sleeve_names:
                if isinstance(item, str):
                    item_count += 1

        else:
            raise ValueError(f"Non-string name {name} detected!")

    return int(item_count)

count_processed_order #

count_processed_order(df_products: DataFrame) -> int

Internal function to count the total number of items in a processed order form - could make this a bit simpler actually

Source code in plkit/validate/tests.py
def count_processed_order(df_products: _pd.DataFrame) -> int:
    """Internal function to count the total number of items in a
    processed order form - could make this a bit simpler actually"""

    # Check that names column exists
    if "Product Name" not in df_products.columns:
        raise LookupError("Product column not found in input DataFrame")
    else:
        products = df_products["Product Name"].to_list()
        # Drop NaN rows
        products = [product for product in products if isinstance(product, str)]

    # Sum over all size cells that contain item counts
    sum = df_products.iloc[0 : len(products), 3:-2].sum(axis=1)

    return int(sum.sum())

count_processed_back_personalisations #

count_processed_back_personalisations(
    df_personal: DataFrame,
) -> int

Internal function to count the total number of requested back name personalisations

Source code in plkit/validate/tests.py
def count_processed_back_personalisations(df_personal: _pd.DataFrame) -> int:
    """Internal function to count the total number of requested back
    name personalisations"""

    back_names = df_personal["Name (back personalisation)"].to_list()
    # Remove NaN rows
    back_names = [back_name for back_name in back_names if isinstance(back_name, str)]

    return len(back_names)

count_processed_sleeve_personalisations #

count_processed_sleeve_personalisations(
    df_personal: DataFrame,
) -> int

Internal function to count the total number of requested back name personalisations

Source code in plkit/validate/tests.py
def count_processed_sleeve_personalisations(df_personal: _pd.DataFrame) -> int:
    """Internal function to count the total number of requested back
    name personalisations"""

    sleeve_names = df_personal["Initials (sleeve personalisation)"].to_list()
    # Remove NaN rows
    sleeve_names = [
        sleeve_name for sleeve_name in sleeve_names if isinstance(sleeve_name, str)
    ]

    return len(sleeve_names)