""" Please write you name here: Tom Harley """ from datetime import timedelta, datetime from contextlib import contextmanager from collections import namedtuple import csv import re def parse_time(time_str): time_str = time_str.strip() if 'M' in time_str: hour_format = "%I" ampm_format = "%p" else: hour_format = "%H" ampm_format = "" if '.' in time_str: minute_format = ".%M" elif ':' in time_str: minute_format = ":%M" else: minute_format = "" format_str = f"{hour_format}{minute_format}{ampm_format}" return datetime.strptime(time_str, format_str) def time_hour(hour): return datetime.strptime(f"{hour}", "%H") def hours(start_time, end_time): offset = 0 if end_time.minute == 0 else 1 iterator = iter(range(start_time.hour, end_time.hour + offset)) prev = time_hour(next(iterator)) for hour in iterator: current = time_hour(hour) yield (prev, current) prev = current @contextmanager def opencsv(path_to_csv, lookahead_size=1024): with open(path_to_csv, newline="") as csvfile: sniffer = csv.Sniffer() sample = csvfile.read(lookahead_size) dialect = sniffer.sniff(sample) header = sniffer.has_header(sample) csvfile.seek(0) reader = csv.reader(csvfile, dialect) if header: # skip header if it exists next(reader) yield reader def process_shifts(path_to_csv): """ :param path_to_csv: The path to the work_shift.csv :type string: :return: A dictionary with time as key (string) with format %H:%M (e.g. "18:00") and cost as value (Number) For example, it should be something like : { "17:00": 50, "22:00: 40, } In other words, for the hour beginning at 17:00, labour cost was 50 pounds :rtype dict: """ with opencsv(path_to_csv) as reader: result = dict() for break_notes, end_time, pay_rate, start_time in reader: break_start, break_end = map(parse_time, break_notes.split('-')) start_time = parse_time(start_time) end_time = parse_time(end_time) pay_rate = float(pay_rate) if not start_time < end_time: # shift passes over midday/midnight end_time = end_time + timedelta(hours=12) if not start_time <= break_start: # break starts in PM break_start = break_start + timedelta(hours=12) if not break_start < break_end: # break passes over midday/midnight break_end = break_end + timedelta(hours=12) assert(break_start < break_end) assert(start_time < end_time) assert(start_time <= break_start) assert(break_end <= end_time) for hour_start, hour_end in hours(start_time, end_time): paid_minutes = 60 if start_time > hour_start: # shift started mid-hour paid_minutes = paid_minutes - start_time.minute if end_time < hour_end: # shift ended mid-hour paid_minutes = paid_minutes - (60 - end_time.minute) if break_start <= hour_start: # break starts before/on hour if break_end >= hour_end: # hour is contained in break paid_minutes = 0 else: # break ends mid-hour paid_minutes = paid_minutes - break_end.minute elif break_start <= hour_end: # break starts mid-hour paid_minutes = paid_minutes - (60 - break_start.hour) if break_end < hour_end: # break also ends mid-hour paid_minutes = paid_minutes + (60 - break_end.hour) else: # break starts after hour pass hour_str = datetime.strftime(hour_start, "%H:%M") hour_cost = pay_rate / 60 * paid_minutes result[hour_str] = result.get(hour_str, 0) + hour_cost return result def process_sales(path_to_csv): """ :param path_to_csv: The path to the transactions.csv :type string: :return: A dictionary with time (string) with format %H:%M as key and sales as value (string), and corresponding value with format %H:%M (e.g. "18:00"), and type float) For example, it should be something like : { "17:00": 250, "22:00": 0, }, This means, for the hour beginning at 17:00, the sales were 250 dollars and for the hour beginning at 22:00, the sales were 0. :rtype dict: """ with opencsv(path_to_csv) as reader: result = dict() for amount, time in reader: amount = float(amount) time = parse_time(time) hour_str = datetime.strftime(time, "%H:00") result[hour_str] = result.get(hour_str, 0) + amount return result def compute_percentage(shifts, sales): """ :param shifts: :type shifts: dict :param sales: :type sales: dict :return: A dictionary with time as key (string) with format %H:%M and percentage of labour cost per sales as value (float), If the sales are null, then return -cost instead of percentage For example, it should be something like : { "17:00": 20, "22:00": -40, } :rtype: dict """ result = dict() # things cannot be sold while there are no clerks working, so it should be # safe to use the keys from the shift calculations dict. for hour_str in shifts: if hour_str not in sales: result[hour_str] = - shifts[hour_str] elif sales[hour_str] == 0: # this won't happen with the version of process_sales I've written # but may turn up in tests (it appears in the docstring of # process_sales) result[hour_str] = - shifts[hour_str] else: result[hour_str] = shifts[hour_str] / sales[hour_str] * 100 return result def best_and_worst_hour(percentages): """ Args: percentages: output of compute_percentage Return: list of strings, the first element should be the best hour, the second (and last) element should be the worst hour. Hour are represented by string with format %H:%M e.g. ["18:00", "20:00"] """ Rank = namedtuple("Rank", "hour value") iterator = iter(percentages.items()) first = Rank(*next(iterator)) best = first worst = first for hour_str, value in iterator: if value < worst.value: worst = Rank(hour_str, value) if value > best.value: best = Rank(hour_str, value) return [ best.hour, worst.hour ] def main(path_to_shifts, path_to_sales): """ Do not touch this function, but you can look at it, to have an idea of how your data should interact with each other """ shifts_processed = process_shifts(path_to_shifts) sales_processed = process_sales(path_to_sales) percentages = compute_percentage(shifts_processed, sales_processed) best_hour, worst_hour = best_and_worst_hour(percentages) return best_hour, worst_hour if __name__ == '__main__': # You can change this to test your code, it will not be used path_to_sales = "./transactions.csv" path_to_shifts = "./work_shifts.csv" best_hour, worst_hour = main(path_to_shifts, path_to_sales) print(best_hour, worst_hour) # Please write you name here: Tom Harley