Calories Tracker Project — Round 4
We have one report. Let's build the second — and raise the bar a little.
The detailed view shows us what we ate. The weekly summary shows us how much — and whether we stayed on target. Calories consumed per day, compared against a personal goal. Math enters the picture.
We start the same way we always do.
# def add_food_item(): [...]
# def last_days_details(): [...]
# def print_details(): [...]
def last_days_sum():
pass
And we accelerate. We've already written similar logic in last_days_details() — we copy it as a starting point and adapt.
def last_days_sum():
today_string = datetime.datetime.now().strftime("%Y-%m-%d")
last_days_dict = {}
for key in food_dict:
if today_string == key.split(",")[0]:
last_days_dict[key] = food_dict[key]
return last_days_dict
Two changes immediately. First — we don't need the full list of values per entry, just the calories: food_dict[key][3]. Second — we don't need the full timestamp as a key. We need the date only: key.split(",")[0]. Multiple entries on the same day should add up, not overwrite each other.
That means we need a counter per day. We initialize it to 0 the first time we see a date, then accumulate. This is the correct pattern:
def last_days_sum():
today_string = datetime.datetime.now().strftime("%Y-%m-%d")
last_days_dict = {}
for key in food_dict:
if today_string == key.split(",")[0]:
if key.split(",")[0] not in last_days_dict:
last_days_dict[key.split(",")[0]] = 0
last_days_dict[key.split(",")[0]] += food_dict[key][3]
return last_days_dict
We check if the date already exists in last_days_dict before initializing. If we initialized unconditionally — outside the check — we'd reset to 0 on every iteration, losing everything accumulated before. The if key not in guard makes sure we only create the key once.
Now — two upgrades. We want to look at any reference date, not just today. And we want to compare against a personal calorie target. Both inputs we've already written before — we adapt them here.
def last_days_sum():
today_string = ""
while not today_string:
today_string = input("Reference date (YYYY-MM-DD, e.g. 2025-04-02): ")
while True:
try:
calories_target = int(input("Your daily calories target: "))
if calories_target > 0:
break
print("Target must be greater than 0.")
except ValueError:
print("Invalid calories target. Enter a whole number.")
last_days_dict = {}
for key in food_dict:
if today_string == key.split(",")[0]:
if key.split(",")[0] not in last_days_dict:
last_days_dict[key.split(",")[0]] = 0
last_days_dict[key.split(",")[0]] += food_dict[key][3]
return last_days_dict
At this point, last_days_dict holds one integer per day — the total calories consumed. But we need more than a number. We need the number and a verdict. So we loop through the dictionary a second time and replace each integer value with a list that holds both.
The key stays the same. The value changes type — from int to list. Python allows this. A dictionary doesn't enforce what type its values are. We use that flexibility deliberately here.
for key in last_days_dict:
if last_days_dict[key] <= calories_target:
cal_value = last_days_dict[key]
last_days_dict[key] = [cal_value, "On target! Great job!"]
else:
cal_value = last_days_dict[key]
cal_diff = cal_value - calories_target
last_days_dict[key] = [cal_value, "Over target by " + str(cal_diff) + " kcal."]
return last_days_dict
Note the correction on cal_diff: we subtract calories_target from cal_value — not the other way around. We want to know how many calories over the target we went, not zero.
It feels a little like training a model — feeding data in, getting a labeled output back. It isn't, of course. But it's not entirely different either. Large things are built from small concepts. This is one of them.
The full function:
def last_days_sum():
today_string = ""
while not today_string:
today_string = input("Reference date (YYYY-MM-DD, e.g. 2025-04-02): ")
while True:
try:
calories_target = int(input("Your daily calories target: "))
if calories_target > 0:
break
print("Target must be greater than 0.")
except ValueError:
print("Invalid calories target. Enter a whole number.")
last_days_dict = {}
for key in food_dict:
if today_string == key.split(",")[0]:
if key.split(",")[0] not in last_days_dict:
last_days_dict[key.split(",")[0]] = 0
last_days_dict[key.split(",")[0]] += food_dict[key][3]
for key in last_days_dict:
if last_days_dict[key] <= calories_target:
cal_value = last_days_dict[key]
last_days_dict[key] = [cal_value, "On target! Great job!"]
else:
cal_value = last_days_dict[key]
cal_diff = cal_value - calories_target
last_days_dict[key] = [cal_value, "Over target by " + str(cal_diff) + " kcal."]
return last_days_dict
Now we display it. Same pattern as before — a dedicated print function, calling last_days_sum() internally.
def print_target():
food_log = last_days_sum()
for key in sorted(food_log):
print(f"Date: {key} - {food_log[key][0]} kcal consumed. {food_log[key][1]}")
sorted(food_log) iterates over the dictionary keys in alphabetical order. Our keys are dates in YYYY-MM-DD format — and for this format, alphabetical order and chronological order are identical. So sorted() gives us the days from oldest to newest, with no extra work.
Into the menu:
while True:
option = input("Choose option (1-Add, 2-Show food details, 3-Discipline analysis, q-Quit): ")
if option == "1":
add_food_item()
print("Food log added successfully!")
elif option == "2":
if len(last_days_details()):
print_details()
else:
print("No data available.")
elif option == "3":
if len(last_days_sum()):
print_target()
else:
print("No data available.")
elif option == "q":
break
else:
print("Invalid option!")
Option 3 — Discipline analysis. The name was chosen deliberately. Not "calorie report". Not "weekly summary". Discipline — because that's what it measures.
The output for a few days might look like this:
Date: 2025-04-01 - 1840 kcal consumed. On target! Great job!
Date: 2025-04-02 - 2210 kcal consumed. Over target by 210 kcal.
Date: 2025-04-03 - 1950 kcal consumed. On target! Great job!
We're not just collecting data anymore. We're evaluating it.
The fire is holding. We have light, we have heat — and now we're starting to cook with it.
One report left. Then we make everything permanent.