Рубрики
Без рубрики

Как сэкономить время и деньги, создавая автоматический планировщик еды

Автор оригинала: Bert Carremans.

Используйте API в Google Calendar и API в Google, чтобы выбрать правильный рецепт в нужный день.

Вы также подчеркиваете, когда получаете вопрос «То, что на ужин сегодня вечером?» Ты не одинок. Я предполагаю, что это самый задаваемый вопрос, что и часовые удары 4 стр. Решив, что поесть, может быть утомительной рудой. Особенно, когда у вас есть маленькие дети с различными занятиями после школы.

Чтобы не ходить в супермаркет каждый день, мы обычно пишем меню с рецептами на ближайшую неделю. Таким образом, мы можем купить все наши продукты в одном посещении супермаркета. Это экономит нам много времени. Кроме того, это также экономит нам деньги. Это потому, что мы менее подвержены всем продажам Трюки супермаркеты используют Отказ

Поиск рецептов на целую неделю требует некоторого мышления и планирования. Мы должны учитывать предпочтения поведения всех членов семьи. Кроме того, у нас есть ограниченное время, доступное для приготовления приготовления каждый день. Чтобы сделать это проще, я создал автоматический планировщик еды с этими функциями:

  • Извлечь планирование работы для меня и моей жены из наших поделинных календарей Google
  • извлечь наши предпочтительные рецепты из электронной таблицы Google,
  • Повторите некоторые рецепты каждую неделю в тот же день
  • Оставьте одну неделю между между ними до повторения других рецептов
  • Мне нравится готовить больше, чем у моей жены. Так на днях, что я не могу готовить, рецепты должны быть закрыты вовремя
  • Загрузить недельное меню в календаре Google

Давайте прыгнем прямо в.

Использование API Google Calendar API и API Google Pailes

Во-первых, нам нужно Создать новый проект Google Cloud Отказ Прежде чем мы сможем использовать календарь Google и листы в этом проекте, нам нужно включить API. Это очень хорошо объяснено на веб-страницах ниже:

Когда это сделано, мы продолжаем импортировать необходимые пакеты Python.

import config as cfg
import pandas as pd
import numpy as np
from pathlib import Path
from datetime import datetime
from datetime import timedelta
from googleapiclient.discovery import build
from google.oauth2 import service_account

Конфигурация

Для причин конфиденциальности и безопасности я сохраняю несколько параметров в отдельном файле config.py. Мы импортируем файл с псевдонимами CFG Отказ Я буду обсуждать эти параметры дальше ниже с фиктивными ценностями. Вы можете включить их для своего собственного приложения со значениями, относящимися к вашему делу.

Охватывает

С областями, мы определяем уровни доступа для календарей и листов Google. Нам понадобится прочитать и запись доступа к обоим Календари и лист Отказ Таким образом, мы используем URL-адресы ниже.

SCOPES = ['https://www.googleapis.com/auth/calendar'
          , 'https://www.googleapis.com/auth/spreadsheets']

Идентификатор листа Google и диапазон

SPREADSHEET_ID = 
RANGE = 'recepten!A:G'

Нам нужно указать идентификатор листа Google с рецептами. Кроме того, мы указываем диапазон листа, содержащий рецепты.

Вы можете найти удостоверение личности ваших листов Google Щелкнув правой кнопкой мыши на листе в Google Drive. Затем выберите «Получить общую ссылку». Вы можете найти идентификатор после «https://drive.google.com/Open?id=».

В моем листе Google «Recepten» столбцы A до G содержат информацию на каждом рецепте. Скриншот ниже показывает некоторое количество образца. Итак, Диапазон должен быть установлен на “Recepten! A: G” Отказ

IDS Calendar Google

CALENDARID_1 = 
CALENDARID_2 = 
CALENDARID_WEEKMENU = 

Нам нужно указать идентификаторы Calendar Google, чтобы получить события. Убедитесь, что у вас есть доступ ко всем календарям, которые вы хотите включить. Вы можете найти идентификатор, выполнив это Сценарий от API Explorer Отказ

Для этого проекта мы выделим события только двух календарей. Но вы можете адаптировать код в цикл более календарей. Я также создал отдельный календарь для загрузки рецептов.

Мероприятия этикетки

BUSY_EVENTS = []
FREE_EVENTS = []
ALL_EVENTS = BUSY_EVENTS + FREE_EVENTS

Моя жена работает в сдвигах и добавляет их в свой календарь Google с помощью буквных кодов. Например: «B» означает послеобеденный сдвиг. Это событие является одним из Busy_events Отказ

Когда у меня есть выходной, я добавляю «праздник» в свой календарь. Это событие является одним из Free_events.

Все события являются полнодневными событиями в календарях Google. Вы можете использовать свои собственные мероприятия схема меток.

Традиции

TRADITIONS = {   'Thursday' : 'fries'}

С Традиции Я имею в виду, что наша семья имеет несколько дней в неделю, на которой мы готовим определенный рецепт. Как мы из Бельгии, это означает, что есть картофель фри раз в неделю (для нас в четверг). И да, прежде чем вы спросите, это картофель фри с майонезом.

Вы можете указать свои собственные традиции в словаре, с названием дня в качестве ключа и рецепта в качестве значения.

Количество дней, чтобы планировать впереди

Иногда мы не можем пойти в супермаркет в день, когда создано новое меню недели. Нам может понадобиться несколько дней, чтобы планировать заранее. С Nb_days_before Мы отдаваем себя немного провисанию. Это означает, что новое недельное меню будет сгенерировано определенное количество дней до завершения предыдущего недели.

NB_DAYS_BEFORE = 3

Использование учетной записи службы

Мы будем использовать Сервисная учетная запись использовать API в проекте. Файл Credentancys.json – это файл, который вы можете скачать при включении API.

Мы создаем учетные данные кредиты с кодом ниже. Эти учетные данные включают аутентификацию в календарях Google и листе Google.

creds = service_account.Credentials.from_service_account_file("credentials.json", scopes=cfg.SCOPES)

Получение событий календаря Google

Начнем с создания объекта сервиса с построить метод.

service_cal = build('calendar', 'v3', credentials=creds)

Мы заинтересованы только в событиях на предстоящую неделю. Чтобы отфильтровать эти события, мы указываем даты и отформатируем их с помощью изоформат () Отказ Параметры Timemin и Timemax нужен этот формат.

def format_date(date):
    date_time = datetime.combine(date, datetime.min.time())
    date_time_utc = date_time.isoformat() + 'Z'
    return date_time_utc

С методом События (). Список Из объекта сервиса мы извлекаем события. Затем извлеченные события отфильтровываются для занятых и свободных событий. Все остальные события на календарях Google не актуальны в этом проекте. Мы сохраняем дату начала и окончания и резюме событий.

def get_event_date(event, timepoint):
    return event[timepoint].get('dateTime', event[timepoint].get('date'))

def get_events_by_calendarId(service, calendarId, timeMin, timeMax, allEvents):
    events_result = service.events().list(calendarId=calendarId
                                        , timeMin=timeMin
                                        , timeMax=timeMax
                                        , singleEvents=True
                                        , orderBy='startTime').execute()
    events = events_result.get('items', [])    
    events_list = [(get_event_date(e, 'start'), get_event_date(e, 'end'), e['summary'].upper()) 
                   for e in events 
                   if e['summary'].upper() in allEvents]
    return unfold_events_list(events_list)

Некоторые события распространяются более одного дня. Например, когда вы отдыхаете более одного дня. Мы разворачиваем эти многодневные события в ежедневных событиях в пределах подходящей недели.

def unfold_events_list(events_list):
    new_events_list = []
    for e in events_list:
        start = datetime.strptime(e[0], '%Y-%m-%d').date()
        end = datetime.strptime(e[1], '%Y-%m-%d').date()
        delta_days = (end - start).days

        if delta_days > 1:
            for d in range(delta_days):
                unfolded_day = start + timedelta(days=d)
                if unfolded_day >= datetime.now().date() and unfolded_day <= datetime.now().date() + timedelta(days=6):
                    new_events_list.append((unfolded_day, e[2]))
        else:
            new_events_list.append((start, e[2]))
    return new_events_list

Наконец, мы хотим, что Pandas DataFrame с событиями обеих календарей на предстоящую неделю. Чтобы добраться до этого результата, мы преобразуем списки событий в кадры данных и слияние на дату. Мы также добавляем будний день до объединенного кадра данных.

def create_events_df(events_list_1, events_list_2):
    events_df_1 = pd.DataFrame.from_records(events_list_1, columns=['date', 'events_cal_1'])
    events_df_2 = pd.DataFrame.from_records(events_list_2, columns=['date', 'events_cal_2'])
    events_df = events_df_1.merge(events_df_2, on='date', how='outer')
    events_df.date = pd.to_datetime(events_df.date)
    events_df.set_index('date', inplace=True)
    events_df.sort_index(inplace=True)

    dates = list(pd.period_range(START_DAY, NEXT_WEEK, freq='D').values)
    new_idx = []
    for d in dates:
        new_idx.append(np.datetime64(d))

    events_df = events_df.reindex(new_idx)
    events_df.reset_index(inplace=True)
    events_df['weekday'] = events_df.date.apply(lambda x: x.strftime('%A'))
    events_df.set_index('date', inplace=True)
    return events_df

Чтобы убедиться, что мы охватываем все даты наступающей недели, мы используем Preg_range и Reindex Объединенный кадр данных.

Получение рецептов из листа Google

На данный момент у нас есть кадр данных со всеми днями наступающей недели, а события (если таковые имеются), происходящие в двух календарях. Теперь мы можем начать извлечь рецепты из листа Google и назначить рецепт на каждый день. Как и в случае с API Google Calendar, давайте начнем с создания объекта сервиса для API Google Pailets.

service_sheet = build('sheets', 'v4', credentials=creds)

С методом электронные таблицы (). Значения (). Получить Мы можем извлечь рецепты из листа Google.

def get_recipes(service, spreadsheetId, range):
    recipes_result = service.spreadsheets().values().get(spreadsheetId=spreadsheetId, range=range).execute()
    recipes = recipes_result.get('values', [])
    recipes_df = pd.DataFrame.from_records(recipes[1:], columns=recipes[0])
    recipes_df.last_date_on_menu = pd.to_datetime(recipes_df.last_date_on_menu, dayfirst=True)
    recipes_df.set_index('row_number', inplace=True)
    eligible_recipes = recipes_df[ (recipes_df.last_date_on_menu < PREV_WEEK) | (np.isnat(recipes_df.last_date_on_menu)) ]
    return recipes_df, eligible_recipes

Далее мы создаем кадр данных с рецептами. Мне нравится работать с Pandas DataFrames, но вы можете использовать другие структуры данных, конечно, конечно.

row_number это поле, рассчитанное в самом листе Google. Мы используем функцию листа Google Ряд () для этого. Это поможет обновить поле last_date_on_menu в правильном ряду. Мы обновим эту дату, когда рецепт выбран на ближайшую неделю.

Нам нужно убедиться, что рецепт повторяется только через неделю. Итак, мы фильтруем Рецепты_df По last_date_on_menu Отказ Эта дата должна быть пустой или до предыдущей недели.

Генерация недели меню

На этом этапе мы назначаем приемлемый рецепт в каждый день на предстоящей неделе.

def generate_weekmenu(service, events_df, traditions, free_events):
    weekmenu_df = events_df.copy()

    for i, r in events_df.iterrows():
        if r.weekday in traditions.keys():
            weekmenu_df.loc[i, 'recipe'] = traditions[r.weekday]
            weekmenu_df.loc[i, 'description'] = ''
        else:
            if r.weekday in ['Saturday', 'Sunday']:
                row_number = choose_recipe('difficult', i, weekmenu_df, eligible_recipes)
                update_sheet(service, row_number, i.strftime('%d-%m-%Y'), cfg.SPREADSHEET_ID)
            elif r.events_cal_1 in free_events or r.events_cal_2 in free_events \
            or pd.isnull(r.events_cal_1) or pd.isnull(r.events_cal_2):
                row_number = choose_recipe('medium', i, weekmenu_df, eligible_recipes)
                update_sheet(service, row_number, i.strftime('%d-%m-%Y'), cfg.SPREADSHEET_ID)
            else:
                row_number = choose_recipe('easy', i, weekmenu_df, eligible_recipes)
                update_sheet(service, row_number, i.strftime('%d-%m-%Y'), cfg.SPREADSHEET_ID)
    return weekmenu_df

Для учета планирования работы (занятые и бесплатные события) мы будем использовать Сложность каждого рецепта. Случайный рецепт предпочтительных сложности будет добавлен в WeekMenu_df. Наконец, мы бросим его от подходящих рецептов, чтобы избежать дублированных рецептов в той же неделе.

def choose_recipe(difficulty, idx, weekmenu_df, eligible_recipes):
    choice_idx = np.random.choice(eligible_recipes.query("difficulty == '" + difficulty + "'" ).index.values)
    weekmenu_df.loc[idx, 'recipe'] = eligible_recipes.loc[choice_idx, 'recipe']
    weekmenu_df.loc[idx, 'description'] = eligible_recipes.loc[choice_idx, 'description']
    eligible_recipes.drop(choice_idx, inplace=True)
    return choice_idx

Метод электронные таблицы (). Значения (). Обновление Обновляет лист Google.

def update_sheet(service, row_number, date, spreadsheetId):
    range = "recepten!F"  + str(row_number)
    values = [[date]]
    body = {'values' : values}
    result = service.spreadsheets().values().update(spreadsheetId=spreadsheetId
                                                    , range=range
                                                    , valueInputOption='USER_ENTERED'
                                                    , body=body).execute()

Мы повторяем каждый ряд WeekMenu_df Отказ Если в будний день является одной из рабочих дней будней, мы назначаем соответствующий рецепт. Для других будних дней мы применяем следующую логику:

  • В выходные выберите сложный рецепт
  • В течение недели, когда я дома, или у моей жены есть выходной, выбирайте рецепт со средней трудностями
  • В течение недели, когда я или моя жена на работе, выберите простой рецепт

Добавление меню недели в календарь Google

Теперь, когда у нас есть меню на ближайшую неделю, мы можем добавить его как события в календарь Google. Я создал отдельный календарь для этого. Поделитесь этим календарем с Client_email в Complents.json. В настройках вашего календаря вам также необходимо дать ему разрешение на изменения в событиях.

def add_weekmenu_to_calendar(service, weekmenu_df, calendarId):
    for i, r in weekmenu_df.iterrows():
        event = {
        'summary': r.recipe,
        'description': r.description,
        'start': {
            'date': i.date().isoformat(),
            'timeZone': 'Europe/Brussels'
        },
        'end': {
            'date': i.date().isoformat(),
            'timeZone': 'Europe/Brussels'
        }
        }
        event = service.events().insert(calendarId=calendarId, body=event).execute()

Давайте автоматизируем

До сих пор мы учитывали все запрошенные функции для приложения. Но вам все равно придется запустить код вручную, чтобы генерировать меню недели.

Я нашел этот отличный сайт Pythonanywhere Где вы можете запланировать программы Python. Свободная учетная запись для начинающих позволяет ежедневно запланировать одну программу Python. Это именно то, что нам нужно.

Во-первых, нам нужно прошивать все функции вместе и поместить их в файл Python. В этом файле я делаю дополнительную проверку, чтобы увидеть, где мы находимся в текущем меню недели. Я делаю это, глядя на последнюю дату с рецептом в календаре Google с get_date_last_event.

def get_date_last_event(service, calendarId):
    events_result = service.events().list(calendarId=calendarId
                                        , singleEvents=True
                                        , orderBy='startTime').execute()
    date_last_event = events_result.get('items', [])[-1]['start']['date'][:10]
    date_last_event = datetime.strptime(date_last_event, '%Y-%m-%d').date()
    return date_last_event

Эта дата хранится в Date_last_recipe. Если текущий день после Date_last_recipe минус Nb_days_before Мы можем генерировать новое недельное меню.

Вы можете найти полный скрипт на Github Отказ

if __name__ == '__main__':
    # Getting credentials from credentials.json
    CREDS_PATH = Path.cwd() / "weekmenu" / "credentials.json"
    creds = service_account.Credentials.from_service_account_file(CREDS_PATH, scopes=cfg.SCOPES)

    # Creating service objects
    service_cal = build('calendar', 'v3', credentials=creds)
    service_sheet = build('sheets', 'v4', credentials=creds)

    # Defining dates
    DATE_LAST_RECIPE = get_date_last_event(service_cal, cfg.CALENDARID_WEEKMENU) 
    START_DAY = DATE_LAST_RECIPE + timedelta(days=1)
    NEXT_WEEK = START_DAY + timedelta(days=6)
    PREV_WEEK = START_DAY + timedelta(days=-7)
    START_DAY = format_date(START_DAY)
    NEXT_WEEK = format_date(NEXT_WEEK)
    PREV_WEEK = format_date(PREV_WEEK)

    # Getting the recipes from the Google Sheet
    recipes_df, eligible_recipes = get_recipes(service_sheet, cfg.SPREADSHEET_ID, cfg.RANGE)

    # Check if the last weekmenu is still active
    if DATE_LAST_RECIPE - timedelta(days=cfg.NB_DAYS_BEFORE) < datetime.now().date():
        # Getting the events from the Google Calendars
        events_list_1 = get_events_by_calendarId(service_cal, cfg.CALENDARID_1, START_DAY, NEXT_WEEK, cfg.ALL_EVENTS)
        events_list_2 = get_events_by_calendarId(service_cal, cfg.CALENDARID_2, START_DAY, NEXT_WEEK, cfg.ALL_EVENTS)

        # Merge the two events lists
        events_df = create_events_df(events_list_1, events_list_2)

        # Generating the weekmenu
        weekmenu_df = generate_weekmenu(service_sheet, events_df, cfg.TRADITIONS, cfg.FREE_EVENTS)

        # Adding the weekmenu to a Google Calendar
        add_weekmenu_to_calendar(service_cal, weekmenu_df, cfg.CALENDARID_WEEKMENU)
        print('Week menu is added to Google Calendar')
    else:
        print('Program stopped. Last week menu is not finished yet.')

На Pythonanywhere я создал подпапку недели меню. Я загрузил следующие файлы config.py, generate_weekmenu.py и complents.json.

Затем я планирую ежедневную задачу, которая запускает сценарий Generate_weekMenu.py в разделе задач. И Voilà, мы все набор.

Результат

После первого пробега скрипта у нас есть хорошее меню в нашем общем календаре Google.

Заключение

Этот скрипт учитывает ваш профессиональный график в ваших календарях Google. Он выбирает ваши предпочтительные рецепты из листа Google. И путем планирования скрипта рецепты появляются автоматически в вашем календаре Google. Это освобождает вас от раздражающей хор, чтобы решить, что есть.

Если вы хотите получить его дальше, вот некоторые идеи для тонкой настройки сценария:

  • учитывать время приготовления рецепта
  • разрешить традицию иметь хотя бы одну вегетарианскую еду в неделю
  • Создайте список продуктовых продуктов для рецептов выбора

Я надеюсь, что вам понравилось читать эту историю. Если у вас есть вопросы или предложения о скрипте, вы можете написать комментарий ниже. И если вам понравилось, не стесняйтесь хлопать за это.