Como economizar tempo e dinheiro construindo um planejador automático de refeições

Use as APIs do Google Agenda e do Google Sheets para selecionar a receita certa no dia certo.

Bert Carremans Blocked Desbloquear Seguir Seguindo 11 de janeiro Foto por rawpixel no Unsplash

Você também fica estressado quando recebe a pergunta “o que é para o jantar hoje à noite?” Você não está sozinho. Acho que é a pergunta mais frequente quando o relógio bate às 16h. Decidir o que comer pode ser uma tarefa tediosa. Especialmente quando você tem filhos pequenos com várias atividades após a escola.

Para evitar ir ao supermercado todos os dias, costumamos escrever um cardápio com receitas para a semana seguinte. Dessa forma, podemos comprar todos os nossos mantimentos em uma visita a um supermercado. Isso nos poupa muito tempo. Além disso, também nos poupa dinheiro. Isso porque estamos menos expostos a todos os truques de venda que os supermercados usam .

Encontrar receitas para uma semana inteira requer algum pensamento e planejamento. Temos que levar em conta as preferências alimentares de todos os membros da família. Além disso, temos um tempo limitado disponível para cozinhar todos os dias. Para facilitar isso, criei um planejador automático de refeições com esses recursos:

  • extrair o planejamento de trabalho para mim e minha esposa de nossos calendários compartilhados do Google
  • extrair nossas receitas preferidas de uma planilha do Google,
  • repetir algumas receitas a cada semana no mesmo dia
  • deixe uma semana no meio antes de repetir as outras receitas
  • Eu gosto de cozinhar mais do que minha esposa. Então nos dias em que não posso cozinhar as receitas devem ser curtas no tempo
  • carregar o menu da semana num calendário do Google

Vamos pular direto.

Usando a API do Google Agenda e a API de planilhas do Google

Primeiro, precisamos criar um novo projeto do Google Cloud . Antes de podermos usar o calendário e as planilhas do Google neste projeto, precisamos ativar as APIs. Isso é muito bem explicado nas páginas abaixo:

Quando isso for feito, continuaremos importando os pacotes Python necessários.

 import config como cfg 
importar pandas como pd
import numpy como np
do caminho de importação do pathlib
de datetime import datetime
de datetime import timedelta
da compilação de importação googleapiclient.discovery
de google.oauth2 import service_account

Configuração

Por questões de privacidade e segurança, mantenho alguns parâmetros em um arquivo config.py separado. Nós importamos o arquivo com o alias cfg . Discutirei esses parâmetros mais abaixo com valores fictícios. Você pode incluí-los em seu próprio aplicativo com valores relevantes para o seu caso.

Escopos

Com escopos, definimos os níveis de acesso para as agendas e planilhas do Google. Precisamos de acesso de leitura e gravação aos calendários e à planilha . Assim, usamos as URLs abaixo.

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

ID e intervalo da planilha do Google

 SPREADSHEET_ID = <seu ID de folha do Google> 
RANGE = 'recepten! A: G'

Precisamos especificar o ID da planilha do Google com as receitas. Além disso, especificamos o intervalo de folhas que contém as receitas.

Você pode encontrar o ID de suas planilhas do Google clicando com o botão direito do mouse na planilha do Google Drive. Em seguida, selecione "Obter link compartilhável". Você pode encontrar o ID após "https://drive.google.com/open?id=".

Na minha folha do Google "recepten", as colunas A a G contêm informações sobre cada receita. A captura de tela abaixo mostra alguns exemplos de conteúdo. Então RANGE precisa ser configurado para “recepten! A: G” .

IDs do Google Agenda

 CALENDARID_1 = <Seu ID do Google Agenda> 
CALENDARID_2 = <ID do Google Agenda do seu parceiro>
CALENDARID_WEEKMENU = <ID do Calendário Google para o menu da semana>

Precisamos especificar os IDs do Google Agenda para obter os eventos. Certifique-se de ter acesso a todos os calendários que você deseja incluir. Você pode encontrar o ID executando este script no APIs Explorer .

Para este projeto, vamos extrair os eventos de apenas dois calendários. Mas você pode adaptar o código para fazer um loop em mais calendários. Eu também criei um calendário separado para carregar as receitas.

Rótulos de eventos

 BUSY_EVENTS = [<Rótulos de eventos agendados do calendário>] 
FREE_EVENTS = [<Labels of free calendar events>]
ALL_EVENTS = BUSY_EVENTS + FREE_EVENTS

Minha esposa trabalha em turnos e os adiciona ao Google Agenda usando códigos de letras. Por exemplo: “B” significa o turno da tarde. Este evento é um dos BUSY_EVENTS .

Quando tenho um dia de folga, adiciono “HOLIDAY” ao meu calendário. Este evento é um dos FREE_EVENTS.

Todos os eventos são de dia inteiro nos Google Calendars. Você pode usar seu próprio esquema de rótulos de evento.

Tradições

 TRADITIONS = { 
'Quinta-feira': 'batatas fritas'
}

Com TRADITIONS , quero dizer que nossa família tem alguns dias na semana em que preparamos uma certa receita. Como somos da Bélgica, isso significa comer batata frita uma vez por semana (para nós na quinta-feira). E sim, antes que você pergunte, isso é frituras com maionese.

Você pode especificar suas próprias tradições em um dicionário, com o nome do dia como a chave e a receita como o valor.

Número de dias para planejar com antecedência

Às vezes não podemos ir ao supermercado no dia em que um novo menu de semana é criado. Podemos precisar de alguns dias para planejar com antecedência. Com NB_DAYS_BEFORE nos damos alguma folga. Isso significa que o novo menu da semana será gerado um certo número de dias antes que o menu da semana anterior tenha terminado.

 NB_DAYS_BEFORE = 3 

Usando uma conta de serviço

Usaremos uma conta de serviço para usar as APIs no projeto. O arquivo credentials.json é o arquivo que você pode baixar ao ativar as APIs.

Criamos credenciais creds com o código abaixo. Essas credenciais permitem a autenticação nas agendas do Google e na planilha do Google.

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

Como obter os eventos do Google Agenda

Começamos criando o objeto de serviço com o método de build .

 service_cal = build ('calendário', 'v3', credenciais = creds) 

Estamos interessados apenas nos eventos da próxima semana. Para filtrar esses eventos, especificamos as datas e as formatamos com isoformat() . Os parâmetros timeMin e timeMax precisam deste formato.

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

Com o método events (). List do objeto de serviço, extraímos os eventos. Os eventos extraídos são então filtrados para os eventos OCUPADO e LIVRE. Todos os outros eventos no Google Calendars não são relevantes neste projeto. Mantemos a data de início e término e o resumo dos eventos.

 def get_event_date (evento, ponto no tempo): 
evento de retorno [timepoint] .get ('dateTime', evento [timepoint] .get ('date'))

def get_events_by_calendarId (serviço, calendarId, timeMin, timeMax, allEvents):
events_result = service.events (). list (calendarId = calendarId
, timeMin = timeMin
, timeMax = timeMax
, singleEvents = True
, orderBy = 'startTime'). execute ()
events = events_result.get ('itens', [])
events_list = [(get_event_date (e, 'start'), get_event_date (e, 'end'), e ['resumo']. upper ())
para e em eventos
if e ['summary']. upper () em todos os eventos]
return unfold_events_list (events_list)

Alguns eventos se espalham por mais de um dia. Por exemplo, quando você tira férias por mais de um dia. Desdobramos esses eventos de vários dias em eventos diários no intervalo da próxima semana.

 def unfold_events_list (events_list): 
new_events_list = []
para e em 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

se delta_days> 1:
para d no intervalo (delta_days):
unfolded_day = start + timedelta (dias = d)
if unfolded_day> = datetime.now (). date () e unfolded_day <= datetime.now (). date () + timedelta (dias = 6):
new_events_list.append ((unfolded_day, e [2]))
else :
new_events_list.append ((start, e [2]))
return new_events_list

Finalmente, queremos um DataFrame do Pandas com os eventos de ambos os calendários para a próxima semana. Para chegar a esse resultado, convertemos as listas de eventos em quadros de dados e mesclamos na data. Também adicionamos o dia da semana ao quadro de dados mesclados.

 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', como = 'outer')
events_df.date = pd.to_datetime (events_df.date)
events_df.set_index ('date', inplace = True )
events_df.sort_index (inplace = True )

datas = list (pd.period_range (valores de START_DAY, NEXT_WEEK, freq = 'D').)
new_idx = []
para d em datas:
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 )
retornar events_df

Para garantir que cobrimos todas as datas da próxima semana, usamos period_range e reindex o quadro de dados mesclado.

Obtendo as receitas da planilha do Google

Neste ponto, temos um quadro de dados com todos os dias da semana seguinte e os eventos (se houver) ocorrendo nos dois calendários. Agora podemos começar a extrair as receitas da planilha do Google e atribuir uma receita a cada dia. Assim como na API do Google Agenda, vamos começar criando o objeto de serviço para a Google Sheets API.

 service_sheet = build ('planilhas', 'v4', credenciais = creds) 

Com o método spreadsheets (). Values (). Get podemos extrair as receitas da Planilha Google.

 def get_recipes (serviço, spreadsheetId, intervalo): 
recipes_result = service.spreadsheets (). values (). get (spreadsheetId = spreadsheetId, intervalo = intervalo) .execute ()
recipes = recipes_result.get ('valores', [])
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 )
qualified_recipes = recipes_df [(recipes_df.last_date_on_menu <PREV_WEEK) | (np.isnat (recipes_df.last_date_on_menu))]
return recipes_df, qualified_recipes

Em seguida, criamos um quadro de dados com as receitas. Eu gosto de trabalhar com Pandas DataFrames, mas você pode usar outras estruturas de dados, é claro.

O row_number é um campo calculado na própria Planilha Google. Usamos a função ROW() do Google Sheet para isso. Isso ajudará a atualizar o campo last_date_on_menu na linha correta. Vamos atualizar essa data quando uma receita é escolhida para a próxima semana.

Precisamos ter certeza de que uma receita só é repetida depois de uma semana. Então, nós filtrar recipes_df por last_date_on_menu . Esta data deve estar vazia ou antes da semana anterior.

Gerando o menu da semana

Nesta etapa, atribuiremos uma receita elegível a cada dia da semana seguinte.

 def generate_weekmenu (serviço, events_df, tradições, free_events): 
weekmenu_df = events_df.copy ()

para i, r em events_df.iterrows ():
se r.weekday em traditions.keys ():
weekmenu_df.loc [i, 'receita'] = tradições [r.weekday]
weekmenu_df.loc [i, 'descrição'] = ''
else :
se r.weekday em ['Saturday', 'Sunday']:
row_number = choose_recipe ('difícil', i, weekmenu_df, eligive_recipes)
update_sheet (service, row_number, i.strftime (' % d -% m -% Y'), cfg.SPREADSHEET_ID)
elif r.events_cal_1 em free_events ou r.events_cal_2 em free_events
ou pd.isnull (r.events_cal_1) ou pd.isnull (r.events_cal_2):
row_number = choose_recipe ('medium', i, weekmenu_df, qualified_recipes)
update_sheet (service, row_number, i.strftime (' % d -% m -% Y'), cfg.SPREADSHEET_ID)
else :
row_number = choose_recipe ('easy', i, weekmenu_df, eligive_recipes)
update_sheet (service, row_number, i.strftime (' % d -% m -% Y'), cfg.SPREADSHEET_ID)
return weekmenu_df

Para levar em conta o planejamento do trabalho (eventos OCUPADOS e GRATUITOS), usaremos a difficulty de cada receita. Uma receita aleatória da dificuldade preferida será adicionada ao weekmenu_df. Por fim, descartamos as receitas qualificadas para evitar receitas duplicadas na mesma semana.

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

O método spreadsheets (). Values (). Update atualiza a Planilha Google.

 def update_sheet (serviço, row_number, data, spreadsheetId): 
intervalo = "recepten! F" + str (row_number)
valores = [[data]]
body = {'values': values}
result = service.spreadsheets (). values (). update (spreadsheetId = spreadsheetId
, intervalo = intervalo
, valueInputOption = 'USER_ENTERED'
corpo = corpo) .execute ()

Nós weekmenu_df sobre cada linha de weekmenu_df . Se o dia da semana é um dos dias úteis da TRADIÇÕES, atribuímos a receita correspondente. Para os outros dias da semana, aplicamos a seguinte lógica:

  • No fim de semana, escolha uma receita difícil
  • Durante a semana, quando estou em casa ou minha esposa tem um dia de folga, escolha uma receita com dificuldade média
  • Durante a semana, quando eu ou minha esposa estiver no trabalho, escolha uma receita fácil

Adicionando o menu da semana a um Google Agenda

Agora que temos um menu para a próxima semana, podemos adicioná-lo como eventos a um Google Agenda. Eu criei um calendário separado para isso. Compartilhe este calendário com o client_email em credentials.json. Nas configurações do seu calendário, você também precisa dar permissão para fazer alterações nos eventos.

 def add_weekmenu_to_calendar (serviço, weekmenu_df, calendarId): 
para i, r em weekmenu_df.iterrows ():
event = {
'resumo': r.recipe,
'description': r.description,
'start': {
'date': i.date (). isoformat (),
'fuso horário': 'Europa / Bruxelas'
}
'fim': {
'date': i.date (). isoformat (),
'fuso horário': 'Europa / Bruxelas'
}
}
event = service.events (). insert (calendarId = calendarId, corpo = evento) .execute ()

Vamos automatizar

Até agora, levamos em consideração todos os recursos solicitados para o aplicativo. Mas você ainda teria que executar o código manualmente para gerar o menu da semana.

Eu encontrei este ótimo site PythonAnyWhere onde você pode agendar programas em Python. A conta gratuita Iniciante permite agendar um programa em Python diariamente. Isso é exatamente o que precisamos.

Primeiro, precisamos juntar todas as funções e colocá-las em um arquivo Python. Neste arquivo, faço uma verificação extra para ver onde estamos no menu da semana atual. Faço isso observando a última data com uma receita no Google Agenda com get_date_last_event.

 def get_date_last_event (serviço, calendarId): 
events_result = service.events (). list (calendarId = calendarId
, singleEvents = True
, orderBy = 'startTime'). execute ()
date_last_event = events_result.get ('items', []) [- 1] ['início'] ['data']
date_last_event = datetime.strptime (date_last_event, '% Y-% m- % d ') .date ()
return date_last_event

Essa data é armazenada em DATE_LAST_RECIPE. Se o dia atual for posterior a DATE_LAST_RECIPE menos NB_DAYS_BEFORE , poderemos gerar um novo menu semanal.

Você pode encontrar o script completo no Github .

 se __name__ == '__main__': 
# Obtendo credenciais de credentials.json
CREDS_PATH = Path.cwd () / "weekmenu" / "credentials.json"
creds = service_account.Credentials.from_service_account_file (CREDS_PATH, escopos = cfg.SCOPES)

# Criando objetos de serviço
service_cal = build ('calendário', 'v3', credenciais = creds)
service_sheet = build ('planilhas', 'v4', credenciais = creds)

# Definindo datas
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 (dias = 6)
PREV_WEEK = START_DAY + timedelta (dias = -7)
START_DAY = format_date (START_DAY)
NEXT_WEEK = format_date (NEXT_WEEK)
PREV_WEEK = format_date (PREV_WEEK)

# Obtendo as receitas da Planilha Google
recipes_df, qualified_recipes = get_recipes (service_sheet, cfg.SPREADSHEET_ID, cfg.RANGE)

# Verifique se o último menu da semana ainda está ativo
se DATE_LAST_RECIPE - timedelta (days = cfg.NB_DAYS_BEFORE) <datetime.now (). date ():
# Obtendo os eventos das agendas do Google
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)

# Mesclar as duas listas de eventos
events_df = create_events_df (events_list_1, events_list_2)

# Gerando o weekmenu
weekmenu_df = generate_weekmenu (service_sheet, events_df, cfg.TRADITIONS, cfg.FREE_EVENTS)

# Adicionando o weekmenu a um Google Calendar
add_weekmenu_to_calendar (service_cal, weekmenu_df, cfg.CALENDARID_WEEKMENU)
print ('menu de semana é adicionado ao Google Calendar')
else :
print ('Programa parado. O menu da última semana ainda não terminou.')

No PythonAnyOnde criei um menu de semana de subpasta. Eu carreguei os seguintes arquivos config.py, generate_weekmenu.py e credentials.json.

Arquivos de projeto no PythonAnyWhere.com

Em seguida, agendo uma tarefa diária que executará o script generate_weekmenu.py na seção Tarefas. E voilà, estamos todos prontos.

O resultado

Após a primeira execução do script, temos um menu interessante em nossa agenda compartilhada do Google.

Menu de semana automatizado em um Google Agenda compartilhado

Conclusão

Este script leva em conta sua agenda profissional em seus calendários do Google. Ele seleciona suas receitas preferidas de uma planilha do Google. E ao agendar o script, as receitas aparecem de forma automatizada no Google Agenda. Isso libera você da tarefa chata de decidir o que comer.

Se você quiser ir mais longe, aqui estão algumas ideias para ajustar o script:

  • levar em conta o tempo de cozimento de uma receita
  • permitir uma tradição de ter pelo menos uma refeição vegetariana por semana
  • gerar uma lista de compras para as receitas escolhidas

Espero que tenha gostado de ler esta história. Se você tiver dúvidas ou sugestões sobre o roteiro, escreva um comentário abaixo. E se você gostou, fique à vontade para aplaudir.

Texto original em inglês.