236 lines
6.4 KiB
Python
236 lines
6.4 KiB
Python
from pathlib import Path
|
|
from datetime import datetime
|
|
from collections import Counter
|
|
from functools import reduce
|
|
import re
|
|
import string
|
|
|
|
import sys
|
|
|
|
def parse_foods_file():
|
|
path = Path.home() / 'projects' / 'open-journal' / 'foods'
|
|
text = path.read_text()
|
|
foods, recipes = text.split('---')
|
|
|
|
def parse_macro(macro):
|
|
if macro == '...':
|
|
return ('INVALID', 0.0)
|
|
|
|
name, value = macro.split()
|
|
value = float(value.removesuffix('g').removesuffix('kcal'))
|
|
return (name, value)
|
|
|
|
foods = {
|
|
macros[0]: dict(parse_macro(macro) for macro in macros[1:])
|
|
for macros in [food.split('\n') for food in foods.strip().split('\n\n')]
|
|
}
|
|
|
|
def combine_values(fst, snd):
|
|
result = fst.copy()
|
|
for k,v in snd.items():
|
|
if k in fst:
|
|
result[k] += v
|
|
else:
|
|
result[k] = v
|
|
return result
|
|
|
|
def evaluate_ingredients(ingredients):
|
|
result = {}
|
|
|
|
total_weight = 0.0
|
|
for ingredient in ingredients:
|
|
k,v = parse_macro(ingredient)
|
|
if k == 'TOTAL':
|
|
result[k] = v
|
|
break
|
|
else:
|
|
total_weight += v
|
|
|
|
|
|
food = foods[k]
|
|
|
|
for kk,vv in food.items():
|
|
if kk not in result:
|
|
result[kk] = 0.0
|
|
|
|
result[kk] += vv * (v/100.0)
|
|
|
|
if 'TOTAL' not in result:
|
|
result['TOTAL'] = total_weight
|
|
|
|
return result
|
|
|
|
recipes = {
|
|
ingredients[0]: evaluate_ingredients(ingredients[1:])
|
|
for ingredients in [
|
|
recipe.split('\n') for recipe in recipes.strip().split('\n\n')
|
|
]
|
|
}
|
|
|
|
def get_calories_from_macros(mm):
|
|
calories = 0.0
|
|
for k,v in mm.items():
|
|
calories += v * {
|
|
'Carbs': 4,
|
|
'Fat': 9,
|
|
'Protein': 4
|
|
}.get(k, 0.0)
|
|
return calories
|
|
|
|
#for k,v in foods.items():
|
|
# print(round(v.get('Energy') - get_calories_from_macros(v)), k)
|
|
|
|
return foods, recipes
|
|
|
|
foods, recipes = parse_foods_file()
|
|
|
|
if len(sys.argv) > 1:
|
|
|
|
value, name = sys.argv[1:]
|
|
value = float(value.removesuffix('g'))
|
|
|
|
if name in recipes:
|
|
food = recipes[name]
|
|
|
|
if value == 0.0:
|
|
value = food['TOTAL']
|
|
|
|
food = {k: v*(value/food['TOTAL']) for k,v in food.items()}
|
|
elif name in foods:
|
|
if value == 0.0:
|
|
value = 100
|
|
|
|
food = {k: v*(value/100.0) for k,v in foods[name].items()}
|
|
else:
|
|
breakpoint()
|
|
print(f'ERROR: Invalid diet entry: {content}')
|
|
|
|
from pprint import pprint
|
|
pprint(food)
|
|
|
|
exit(0)
|
|
|
|
|
|
entry_re = re.compile(r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) ', re.MULTILINE)
|
|
diet_re = re.compile(r'@diet (\d+g) ([a-zA-Z]+)')
|
|
|
|
total_entries = 0
|
|
total_words = 0
|
|
word_frequency = Counter()
|
|
|
|
total_csv = [['day', 'entries', 'words']]
|
|
daily_csv = [['day', 'entries', 'words', 'calories', 'carbs', 'fat', 'protein',
|
|
'sugar']]
|
|
entry_csv = [['timestamp', 'words']]
|
|
words_csv = [['word', 'count']]
|
|
|
|
diet_csv = [[
|
|
'timestamp', 'name', 'grams', 'calories', 'carbs', 'fat', 'protein',
|
|
'saturated_fat', 'sugar', 'fiber'
|
|
]]
|
|
|
|
for fpath in sorted((Path.home() / 'workspace' / 'journal').glob('*.md')):
|
|
day = fpath.stem
|
|
header, *tmp = entry_re.split(fpath.read_text())
|
|
entries = list(zip(tmp[::2], tmp[1::2]))
|
|
|
|
daily_entries = len(entries)
|
|
daily_words = 0
|
|
daily_calories = 0.0
|
|
daily_protein = 0.0
|
|
daily_carbs = 0.0
|
|
daily_fat = 0.0
|
|
daily_sugar = 0.0
|
|
|
|
for (timestamp, content) in sorted(entries, key=lambda x: x[0]):
|
|
timestamp = int(datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').timestamp())
|
|
|
|
content = '\n'.join(
|
|
part.replace('\n', ' ')
|
|
for part in content.split('\n\n')
|
|
)
|
|
|
|
for diet in diet_re.finditer(content):
|
|
value, name = diet.groups()
|
|
value = float(value.removesuffix('g'))
|
|
|
|
if name in recipes:
|
|
food = recipes[name]
|
|
|
|
if value == 0.0:
|
|
value = food['TOTAL']
|
|
|
|
food = {k: v*(value/food['TOTAL']) for k,v in food.items()}
|
|
elif name in foods:
|
|
if value == 0.0:
|
|
value = 100
|
|
|
|
food = {k: v*(value/100.0) for k,v in foods[name].items()}
|
|
else:
|
|
breakpoint()
|
|
print(f'ERROR: Invalid diet entry: {content}')
|
|
continue
|
|
|
|
|
|
diet_csv.append((
|
|
timestamp,
|
|
name,
|
|
value,
|
|
round(food.get('Energy', 0.0), 2),
|
|
round(food.get('Carbs', 0.0), 2),
|
|
round(food.get('Fat', 0.0), 2),
|
|
round(food.get('Protein', 0.0), 2),
|
|
round(food.get('SaturatedFat', 0.0), 2),
|
|
round(food.get('Sugar', 0.0), 2),
|
|
round(food.get('Fiber', 0.0), 2),
|
|
))
|
|
|
|
daily_calories += food.get('Energy', 0.0)
|
|
daily_protein += food.get('Protein', 0.0)
|
|
daily_fat += food.get('Fat', 0.0)
|
|
daily_carbs += food.get('Carbs', 0.0)
|
|
daily_sugar += food.get('Sugar', 0.0)
|
|
|
|
words = ''.join(
|
|
c if c in string.ascii_letters+"'" else ' '
|
|
for c in content.lower()
|
|
).split()
|
|
|
|
word_frequency.update(words)
|
|
|
|
entry_words = len(words)
|
|
daily_words += entry_words
|
|
|
|
entry_csv.append([timestamp, entry_words])
|
|
|
|
daily_macros = daily_protein + daily_fat + daily_carbs
|
|
|
|
daily_csv.append([
|
|
day,
|
|
daily_entries,
|
|
daily_words,
|
|
round(daily_calories, 2),
|
|
round(100 * (daily_carbs / daily_macros) if daily_carbs else 0, 2),
|
|
round(100 * (daily_fat / daily_macros) if daily_fat else 0, 2),
|
|
round(100 * (daily_protein / daily_macros) if daily_protein else 0, 2),
|
|
round(daily_protein, 2),
|
|
round(daily_sugar, 2)
|
|
])
|
|
|
|
total_entries += daily_entries
|
|
total_words += daily_words
|
|
|
|
total_csv.append([day, total_entries, total_words])
|
|
|
|
words_csv += word_frequency.most_common()
|
|
|
|
def write_csv(fname, csv):
|
|
with open(fname, 'w') as fp:
|
|
fp.write('\n'.join(','.join(str(x) for x in row) for row in csv))
|
|
|
|
write_csv('total.csv', total_csv)
|
|
write_csv('daily.csv', daily_csv)
|
|
write_csv('entry.csv', entry_csv)
|
|
write_csv('words.csv', words_csv)
|
|
write_csv('diet.csv', diet_csv)
|