Code to help with the re-arranging of my life in 2024.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

30 KiB

Meal Planner

Setup Common Lisp Environment

I’ve copied the following code block over from other files. Run it if this is your first file open in the session.

  (ql:quickload :com.inuoe.jzon) ; JSON parser.
  (ql:quickload :dexador)        ; HTTP requests.
  (ql:quickload :plump)          ; HTML/XML parser.
  (ql:quickload :lquery)         ; HTML/DOM manipulation.
  (ql:quickload :lparallel)      ; Parallel programming.
  (ql:quickload :cl-ppcre)       ; RegEx. library.
  (ql:quickload :plot/vega)      ; Vega plotting library.
  (ql:quickload :lisp-stat)      ; Stat's library.
  (ql:quickload :data-frame)     ; Data frame library eqv. to Python's Numpy.
  (ql:quickload :str)            ; String library, expands on 'string' library.

Seven Day Meal Planner (AI Generated)

The plan below was formed using Bing Copilot, and copied verbatim. I've decided to go this route because I don't know enough about nutrition and I don't really want to spend the time looking into the various websites/resources and what they recommend. I did give Copilot my height and weight when it built out this plan. I don't know if that changes things too much. The thing I'm most concerned with it how much it'll cost.

Day 1:

Breakfast: Scrambled eggs with smoked salmon and a slice of whole grain toast. Lunch: Grilled chicken salad with lots of mixed vegetables. Dinner: Steak with quinoa and steamed broccoli. Snacks: Greek yogurt, a handful of almonds. Day 2:

Breakfast: Protein smoothie with banana, spinach, and a scoop of protein powder. Lunch: Tuna salad with a side of whole grain crackers. Dinner: Baked salmon with sweet potato and asparagus. Snacks: Cottage cheese, a piece of fruit. Day 3:

Breakfast: Oatmeal with a scoop of protein powder and a handful of berries. Lunch: Turkey wrap with whole grain tortilla and lots of veggies. Dinner: Grilled shrimp with brown rice and green beans. Snacks: Hard-boiled eggs, a handful of walnuts. Day 4:

Breakfast: Greek yogurt with granola and a handful of berries. Lunch: Chicken stir-fry with lots of mixed vegetables. Dinner: Baked cod with quinoa and steamed zucchini. Snacks: Protein shake, a piece of fruit. Day 5:

Breakfast: Scrambled eggs with turkey bacon and a slice of whole grain toast. Lunch: Quinoa salad with grilled chicken and lots of mixed vegetables. Dinner: Steak with sweet potato and steamed Brussels sprouts. Snacks: Greek yogurt, a handful of almonds. Day 6:

Breakfast: Protein smoothie with banana, spinach, and a scoop of protein powder. Lunch: Tuna salad with a side of whole grain crackers. Dinner: Baked salmon with brown rice and asparagus. Snacks: Cottage cheese, a piece of fruit. Day 7:

Breakfast: Oatmeal with a scoop of protein powder and a handful of berries. Lunch: Turkey wrap with whole grain tortilla and lots of veggies. Dinner: Grilled shrimp with quinoa and green beans. Snacks: Hard-boiled eggs, a handful of walnuts.

Breakdown of Meal Planner

Below is a list of each item in the meal planner.

  DATA="Eggs
  Salmon
  Whole Grain Toast
  Chicken
  Vegetables
  Steak
  Quinoa
  Broccoli
  Greek Yogurt
  Almonds
  Banana Protein Smoothie
  Spinach
  Scoop Protein Powder
  Tuna
  Salad
  Whole Grain Crackers
  Salmon
  Sweet Potato
  Asparagus
  Cottage Cheese
  Fruit
  Oatmeal
  Protein Powder
  Berries
  Turkey
  Whole Grain Tortilla
  Vegetables
  Prawns
  Brown Rice
  Green Beans
  Eggs
  Walnuts
  Greek Yogurt
  Granola
  Berries
  Chicken
  Vegetables
  Cod
  Quinoa
  Courgette
  Protein Shake
  Fruit
  Eggs
  Turkey
  Bacon
  Whole Grain Toast
  Quinoa
  Salad
  Chicken
  Vegetables
  Steak
  Sweet Potato
  Brussels Sprouts
  Greek Yogurt
  Almonds
  Protein Smoothie
  Banana
  Spinach
  Protein Powder
  Tuna
  Salad
  Whole Grain Crackers
  Salmon
  Brown Rice
  Asparagus
  Cottage Cheese
  Fruit
  Oatmeal
  Protein Powder
  Berries
  Turkey
  Whole Grain Tortilla
  Vegetables
  Prawns
  Quinoa
  Green Beans
  Eggs
  Walnuts"
  echo "$DATA" > working-data/meal-planner-ingredients.txt
  sort working-data/meal-planner-ingredients.txt \
      | uniq -c > working-data/meal-planner-servings.txt

Having written that out to a file, I can see have many times each item was listed. Note, this quantity value represents the number of serving in a given week. It doesn't specify the quantity of the serving for each item. For example, 1 Bacon doesn't mean one strip of bacon, it means one serving of bacon in the week.

  head working-data/meal-planner-servings.txt
      2 Almonds
      2 Asparagus
      1 Bacon
      1 Banana
      1 Banana Protein Smoothie
      3 Berries
      1 Broccoli
      2 Brown Rice
      1 Brussels Sprouts
      3 Chicken
  echo "SERVING,INGREDIENT" > working-data/meal-planner-servings.csv
  while IFS= read -r line
  do
      csv_line=$(echo "$line" | awk '{$1=$1; print $1 "," substr($0, index($0,$2))}')
      echo "$csv_line"
  done < working-data/meal-planner-servings.txt >> working-data/meal-planner-servings.csv
  echo "[[file:./working-data/meal-planner-servings.csv]]"

/craig.oates/overhaul2024/src/branch/master/working-data/meal-planner-servings.csv

Manually Adjust Serving Quantities

The original version of this file has semantically duplicated data in it, which the code isn't really great at detecting. So, I've made minor adjustments, like bundling up the ’Protein’ entries into one. I, also, replace ’Whole Grain’ with ’Wholemeal’ because I don't think I've ever seen anything labelled as ’Whole Grain’ in the UK, making it harder to search for these types of items on the various shopping websites.

INGREDIENT SERVING
Almonds 2
Asparagus 2
Bacon 1
Banana 1
Banana Protein Smoothie 1
Berries 3
Broccoli 1
Brown Rice 2
Brussels Sprouts 1
Chicken 3
Cod 1
Cottage Cheese 2
Courgette 1
Eggs 4
Fruit 3
Granola 1
Greek Yogurt 3
Green Beans 2
Porridge 2
Prawns 2
Protein Powder 6
Quinoa 4
Salad 3
Salmon 3
Spinach 2
Steak 2
Sweet Potato 2
Tuna 2
Turkey 3
Vegetables 5
Walnuts 2
Wholemeal Crackers 2
Wholemeal Bread 2
Wholemeal Tortilla 2
  (let ((filepath #P"working-data/meal-planner-servings-adjusted.csv"))
    (with-open-file (stream filepath :direction :output :if-exists :supersede)
      (format stream "INGREDIENT,SERVING~%")
      (dolist (row table)
        (format stream "~{~a~^,~}~%" row)))
    (format t "[[file:./~a]]" filepath))

/craig.oates/overhaul2024/src/branch/master/working-data/meal-planner-servings-adjusted.csv

  (lisp-stat:defdf *ingredient-servings*
    (lisp-stat:read-csv #P"working-data/meal-planner-servings-adjusted.csv"))
#<DATA-FRAME:DATA-FRAME (34 observations of 2 variables)>
  (format t "~d servings of ~d ingredients."
          (lisp-stat:sum *ingredient-servings*:serving)
          (lisp-stat:length *ingredient-servings*:ingredient))
78 servings of 34 ingredients.
  78 / 7
11.1428571429

I can't tell if 11 items a day is a lot or not (2024-03-04 Mon).

  (vega:defplot ing-serv
    `(:title "Weekly Ingredient Servings"
      :description "Percentage of how much each ingredient is served in a week."
      :data ,*ingredient-servings*
      :width 600
      :height 300
      :mark (:type :bar)
      :encoding (:x (:field :serving :title "No. of Servings in Week." :type :quantitative)
                 :color (:field :ingredient :type :nominal :title "Ingredient" :legend (:symbol-limit 100))
                 :tooltip (:field :ingredient))))
  (vega:write-html ing-serv "renders/ingredient-servings.html")

/craig.oates/overhaul2024/src/branch/master/renders/ingredient-servings.html

  mv ~/Downloads/visualization.png ./renders/ingredient-servings.png

/craig.oates/overhaul2024/src/branch/master/renders/ingredient-servings.png

The chart above shows the diet is fairly well balanced. With that said, the chicken and vegetable servings are noticeably bigger than the others – followed by eggs.

Ingredients Availability

I'm going to use csvlook to output the data into a org-mode table, which I will then manipulate manually. The csvlook snippet is to just get me started.

  csvlook working-data/meal-planner-servings.csv
  • Merged Scoop Protein Powder, Protein Powder, Protein Shake and Protein Smoothie into one ingredient.
  • I replaced Whole Grain Toast with Wholemeal Bread, I think this is a UK branding issue (I’ve only ever seen ’wholemeal’, never ’wholegrain’).
  • Replaced Whole Grain Tortilla with Wholemeal Tortilla, for the same reason as the bread.
  • 0 = False (i.e. Not sold by that supermarket)
  • 1 = True
  • Used C-c +, C-y to provide the column totals (instead of using formulas).
INGREDIENT TESCO SAINSBURYS MARKS-AND-SPENCER ASDA CO-OP WAITROSE BOOTHS MORRISONS
Almonds 1 1 0 1 1 1 0 1
Asparagus 1 1 0 1 0 1 0 1
Bacon 1 1 0 1 1 1 0 1
Banana 1 1 0 1 1 1 0 1
Berries 1 1 0 1 1 1 0 1
Broccoli 1 1 0 1 0 1 0 1
Brown Rice 1 1 0 1 1 1 0 1
Brussels Sprouts 1 1 0 1 0 1 0 1
Chicken 1 1 0 1 1 1 0 1
Cod 1 1 0 1 0 1 0 1
Cottage Cheese 1 1 0 1 1 1 0 1
Courgette 1 1 0 1 1 1 0 1
Eggs 1 1 0 1 1 1 0 1
Fruit 1 1 0 1 1 1 0 1
Granola 1 1 0 1 1 1 0 1
Greek Yogurt 1 1 0 1 1 1 0 1
Green Beans 1 1 0 1 0 1 0 1
Oatmeal (Porridge) 1 1 0 1 1 1 0 1
Prawns 1 1 0 1 1 1 0 1
Protein Powder 1 1 0 1 0 0 0 1
Quinoa 1 1 0 1 0 1 0 1
Salad (in mixed bag) 1 1 0 1 1 1 0 1
Salmon 1 1 0 1 1 1 0 1
Spinach 1 1 0 1 1 1 0 1
Steak 1 1 0 1 1 1 0 1
Sweet Potato 1 1 0 1 1 1 0 1
Tuna 1 1 0 1 1 1 0 1
Turkey 1 1 0 1 0 1 0 1
Vegetables (in mixed bag) 1 1 0 1 0 0 0 1
Walnuts 1 1 0 1 1 1 0 1
Wholegrain Crackers 1 0 0 0 1 1 0 1
Wholemeal Bread 1 1 0 1 1 1 0 1
Wholemeal Tortilla 1 1 0 1 0 1 0 0
Total 33 32 0 32 23 31 0 32

Looks like Tesco is the only one selling all the ingredients. Sainsburys, Asda and Morrisons are viable alternatives.

Marks and Spencer uses Ocado to provide it online shopping service. Having a quick look on their website, I noticed it say there is a ’Minimum spend of £40’. For now, I'm going to not include them in the availability table above. The reason why is because their prices seem more expensive than the others in the table. If the totals for the shops listed above are above £40, I'll come back to Ocado and see how much their prices differ.

Booths just seems to sell recipes or fully prepared stuff from what I can gather on their website… and lots of wine.

Ingredients Quantity Breakdown

I expect I will need to use these values as base values and come back to this file after I've got an idea about what quantities are good and what need changing.

  • 0.25 like values refer to using 1/4 of a pack, of said item (i.e. 0.25 of a packet of nuts).
  • CHICKEN and TURKEY like ingredients refer to chicken breasts, I don’t like cooking whole chickens.
  • EGGS refers to the number of eggs in the serving, not the number of boxes/packs.
DAY ALMONDS ASPARAGUS BACON BANANA BERRIES BROCCOLI BROWN RICE BRUSSELS SPROUTS CHICKEN COD COTTAGE CHEESE COURGETTE EGGS FRUIT GRANOLA GREEK YOGURT GREEN BEANS OATMEAL (PORRIDGE) PRAWNS PROTEIN POWDER QUINOA SALAD (IN MIXED BAG) SALMON SPINACH STEAK SWEET POTATO TUNA TURKEY VEGETABLES (IN MIXED BAG) WALNUTS WHOLEGRAIN CRACKERS WHOLEMEAL BREAD (TOAST) WHOLEMEAL TORTILLA
Monday 0.25 0 0 0 0 0.25 0 0 2 0 0 0 2 0 0 0.25 0 0 0 0 0.25 0.25 1 0 1 0 0 0 0.25 0 0 2 0
Tuesday 0 0.25 0 1 0 0 0 0 0 0 0.25 0 0 1 0 0 0 0 0 0.1 0 0.25 1 0.25 0 1 1 0 0 0 1 2 0
Wednesday 0 0 0 0 0 0 0.5 0 0 0 0 0 2 0 0 0 0.25 0.2 0.5 0 0 0 0 0 0 0 0 2 0.25 0.25 0 0 2
Thursday 0 0 0 0 0.25 0 0 0 2 1 0 0.5 0 1 0.25 0.25 0 0 0 0.1 0.25 0 0 0 0 0 0 0 0.25 0 0 0 0
Friday 0.25 0 4 0 0 0 0 0.25 2 0 0 0 2 0 0 0.25 0 0 0 0 0.25 0.25 0 0 1 1 0 2 0.25 0 0 2 0
Saturday 0 0.25 0 1 0 0 0.5 0 0 0 0.25 0 0 1 0 0 0 0 0 0.1 0 0.25 1 0.25 0 0 1 0 0 0 1 0 0
Sunday 0 0 0 0 0.25 0 0 0 0 0 0 0 2 0 0 0 0.25 0.2 0.5 0.1 0.25 0 0 0 0 0 0 2 0.25 0.25 0 0 2
Total 0.5 0.5 4 2 0.5 0.25 1. 0.25 6 1 0.5 0.5 8 3 0.25 0.75 0.5 0.4 1. 0.4 1. 1. 3 0.5 2 2 2 6 1.25 0.5 2 6 4

2024-03-04 Mon: Going to write this file to disk and work with the CSV file going forward. This table is a bit unwieldy in this ORG file. I've kept this table for reference. Do not expect it to be accurate as I will not update it. All updates will be in the CSV file moving forward.

  (let ((filepath #P"working-data/meal-planner-quantities.csv"))
    (with-open-file (stream filepath :direction :output :if-exists :supersede)
      (dolist (row table)
        (format stream "~{~a~^,~}~%" row)))
    (format t "[[file:./~a]]" filepath))

/craig.oates/overhaul2024/src/branch/master/working-data/meal-planner-quantities.csv

I need to transform the meal-planner-quantities.csv file so I can work out how many of each item I need to buy. For example, the almonds total is 0.5 (at time of writing), this means the nearest package quantity is 1. I can do it using the file above, but I can transform it and build a template, of sorts, so I can quickly run through various shopping websites and compare prices (if I need to).

  # Input CSV file path
  input_csv_file="working-data/meal-planner-quantities.csv"
  # Output CSV file path
  output_csv_file="working-data/meal-planner-quantities-adj.csv"
  # Extract header (first row) and footer (last row)
  header=$(head -n 1 "$input_csv_file")
  footer=$(tail -n 1 "$input_csv_file")
  # Split header and footer into arrays
  IFS=',' read -ra header_array <<< "$header"
  IFS=',' read -ra footer_array <<< "$footer"
  # Create a new CSV file with rows containing header and footer entries
  echo "INGREDIENT,QUANTITY" > "$output_csv_file"
  for ((i = 0; i < ${#header_array[@]}; i++)); do
      echo "${header_array[i]},${footer_array[i]}" >> "$output_csv_file"
  done
  # Remove the 'Total' and 'Day' parts from the CSV file.
  sed -i '2d' "$output_csv_file"
  # Print out link to the CSV file.
  echo "[[file:./$output_csv_file]]"

/craig.oates/overhaul2024/src/branch/master/working-data/meal-planner-quantities-adj.csv

Having transformed the data, you should see something along the lines of the table below.

  # The output will need minor adjustments to its formatting, for org-mode.
  head -n 4 working-data/meal-planner-quantities-adj.csv | csvlook
INGREDIENT QUANTITY
ALMONDS 0.5
ASPARAGUS 0.5
BACON 4.0

Explore Data and Total Costs for Tesco

I'm going to build out the CSV file outside of this file. The reason why is it requires quite a number of function cells to calculate totals, and quite frankly, using org-mode table-formulas can get unwieldy quickly.

/craig.oates/overhaul2024/src/branch/master/working-data/meal-planner-totals-tesco.csv

  csvlook working-data/meal-planner-totals-tesco.csv

The table below was derived from working-data/meal-planner-totals-tesco.csv. I used csvlook to output the table and I've worked on it since then.

INGREDIENT QUANTITY PACKET-QUANTITY TOTAL-PACKETS PRICE-PER-PACKET ITEM-TOTAL
ALMONDS 0.50 250 g 1 3.1 3.10
ASPARAGUS 0.50 180 g 1 1.6 1.60
BACON 4.00 10 1 2.25 2.25
BANANA 2.00 5 1 0.78 0.78
BERRIES 0.50 200 g 1 2.75 2.75
BROCCOLI 0.25 1 1 0.82 0.82
BROWN RICE 1.00 1 kg 1 1.85 1.85
BRUSSELS SPROUTS 0.25 300 g 1 0.58 0.58
CHICKEN 6.00 2 3 2.6 7.80
COD 1.00 2 1 5.6 5.60
COTTAGE CHEESE 0.50 300 g 1 1.55 1.55
COURGETTE 0.50 1 1 0.56 0.56
EGGS 8.00 10 1 2.9 2.90
FRUIT 3.00 6 1 0.95 0.95
GRANOLA 0.25 1 kg 1 2.5 2.50
GREEK YOGURT 0.75 500 g 1 1.1 1.10
GREEN BEANS 0.50 220 g 1 0.89 0.89
OATMEAL (PORRIDGE) 0.40 1 kg 1 0.9 0.90
PRAWNS 1.00 250 g 1 2.69 2.69
PROTEIN POWDER 0.40 500 g 1 18 18.00
QUINOA 1.00 300 g 1 2.8 2.80
SALAD (IN MIXED BAG) 1.00 90 g 1 1.35 1.35
SALMON 3.00 4 1 4 4.00
SPINACH 0.50 250 g 1 1.15 1.15
STEAK 2.00 2 1 4.15 4.15
SWEET POTATO 2.00 1 2 0.42 0.84
TUNA 2.00 1 2 0.55 1.10
TURKEY 6.00 3 2 4.5 9.00
VEGETABLES (IN MIXED BAG) 1.25 1 kg 1 1.65 1.65
WALNUTS 0.50 200 g 1 2.75 2.75
WHOLEGRAIN CRACKERS 2.00 160 g 1 2 2.00
WHOLEMEAL BREAD (TOAST) 6.00 400 g 1 1 1.00
WHOLEMEAL TORTILLA 4.00 8 1 1.4 1.40
TOTAL 92.36

£92.36 is way too high for my liking. £92.36 to feed a single person? I know it's 2024 in the UK but that seems excessive.

  92.36 / 7
13.1942857143

Saying that, £13.19/day, on food, doesn't seem that unrealistic. On top of that, the £13.19/day covers all three meals. Looks like I haven't been paying attention, or I’m naturally finding ways to keep my food costs down.

  92.36 - 18
74.36

Taking away the protein powder (£18 is the cheapest) leaves the total still to high. The quantities the protein powder was listed at suggests this is not a weekly item. On top of that, there are quite a few items which will produce excess waste at the end of the week, if sticking to the meal planner. So, maybe the next step is to work out how much is weekly, fortnightly and monthly? The quantities are accounted for on a weekly basis. The amount of waste isn't enough to carry me into the next week.

  74.36 / 7
10.6228571429

Removing the protein powder from the day spent, brings it down to £10.62/day. Because of how likely I would be buying protein powder, maybe this is a more realistic figure than the £13.92 one above?

  92.36 * 4
369.44

The total spend on food for the month (including weekly protein powder) is going to be around £369.44/month, give-or-take a few days for the uneven amount of days in each month.

  74.36 * 4
297.44

The monthly spend without the weekly protein powder is £297.44, give-or-take a few days. I would still need to buy at least one protein powder a month, though.

  297.44 + 18
315.44

That brings the total (with monthly purchase of protein powder) to £315.44, give-or-take a few days.