diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a180cb --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Purpose + +To keep track of my vitamins, minerals, supplements and prescriptions so I can know when to order more, and to have enough when I do my monthly 'packaging' of them into easy to use containers. + +# Inventory CLI + +```bash +> sup status + +The next fill-up is on , and you won't have enough of + +- x (need quantity x') +- y (need quantity y') +- z (need quantity z') + +> sup fill + +Okay, next fill-up set to (configured in `sup.toml`::FILL_EVERY_X_DAYS) + +# (this is to add to the inventory; any changes to consumption should be done in `supps.toml`) +> sup add +>> name? +>> date? (today) +>> number of bottles? (1) +>> quantity? +>> serving quantity? +>> serving unit? (mg) +``` + +# Configuration + +TODO: talk about product_aliases + +To configure what you take, how much, and when, add entries to `supps.toml`. The fields are configured as such in the `Supp` class: + +```python +name: str +units: t.Literal["caps", "mg", "g", "ml", "mcg", "iu"] = "mg" + +# these are meant to be doses; units are defined below +morning: int | float = 0 +lunch: int | float = 0 +dinner: int | float = 0 +bedtime: int | float = 0 + +days_per_week: int = 7 +winter_only: bool = False +``` diff --git a/main.py b/main.py deleted file mode 100644 index df0f3ef..0000000 --- a/main.py +++ /dev/null @@ -1,85 +0,0 @@ -import collections -from dataclasses import dataclass -import json -import pathlib as pl -import tomllib -import typing as t - -from rich.pretty import pprint as print - - - -def load_config(): - return tomllib.loads(pl.Path("supps.toml").read_text()) - - -def load_ordered_supps(): - return json.loads(pl.Path('supps.json').read_text()) - - -ALIASES = { - 'Naturally Sourced Vitamin E': "vitamin e", - "Natural Vitamin K2 MK-7 with MenaQ7": "k2-mk7", - "Liquid D-3 & MK-7": "d-3", - "Calcium AKG Longevity": "ca-akg", - "MK-7 Vitamin K-2": "k2-mk7", - "Optimized Folate": "methyl folate", - "Extend-Release Magnesium": "magnesium slow release", - "Crucera-SGS": "broccomax", - "B-50": "b complex", - "Vitamin K2, MK-4 (Menatetrenone)": "k2-mk4", - "Super K": "k1", - "Ultimate Omega, Lemon": "epa/dha", - } - -ALIASES_REV = {v: k for k, v in ALIASES.items()} - -class Missing(Exception): - pass - - -def validate_matches() -> None: - missing = [] - config = load_config() - - ordered_supp_names_lower = [i['name'].lower() for i in load_ordered_supps()] - for i in config['supps']: - if ( - not any(i['name'].lower() in ordered_supp_name for ordered_supp_name in ordered_supp_names_lower) - and i['name'].lower() not in ALIASES_REV - ): - missing.append(i['name']) - - if missing: - raise Missing(', '.join(missing)) - - -@dataclass -class Supp: - name: str - morning: int | float = 0 - lunch: int | float = 0 - dinner: int | float = 0 - bedtime: int | float = 0 - per_week: int = 7 - units: t.Literal["caps", "mg", "g", "ml", "mcg", "iu"] = "mg" - winter_only: bool = False - - -# special case: Liquid D-3 & MK-7 -- has d-3 and k-mk7 -# special case: K Complex has K1, MK-4 and MK-7 in it - -""" -{'orderDate': '2024-01-04T23:00:00.000Z', - 'name': 'Naturally Sourced Vitamin E', - 'quantity': 100, - 'quantityUnits': 'caps', - 'servingUnit': 'mg', - 'numUnitsInServing': 134, - 'numBottles': 2}] -""" - -missing_names = validate_matches() - -if missing_names: - print(missing_names) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..02e0f61 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,142 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "typer" +version = "0.9.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.6" +files = [ + {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, + {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "4760c0a423126e044bb2d35b8bff39a0c605f92514ca863afbd72930d00fa619" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4ab8e1e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "sup" +version = "0.1.0" +description = "" +authors = ["Zev Averbach "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +rich = "^13.7.0" +typer = "^0.9.0" +toml = "^0.10.2" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +sup = "sup.cli:app" diff --git a/scrape.js b/scrape_iherb_orders.js similarity index 100% rename from scrape.js rename to scrape_iherb_orders.js diff --git a/src/sup/__init__.py b/src/sup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/sup/__pycache__/__init__.cpython-312.pyc b/src/sup/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..947ca29 Binary files /dev/null and b/src/sup/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/sup/__pycache__/cli.cpython-312.pyc b/src/sup/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000..8130495 Binary files /dev/null and b/src/sup/__pycache__/cli.cpython-312.pyc differ diff --git a/src/sup/__pycache__/main.cpython-312.pyc b/src/sup/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..8f4bac2 Binary files /dev/null and b/src/sup/__pycache__/main.cpython-312.pyc differ diff --git a/src/sup/cli.py b/src/sup/cli.py new file mode 100644 index 0000000..282de48 --- /dev/null +++ b/src/sup/cli.py @@ -0,0 +1,138 @@ +""" +{'orderDate': '2024-01-04T23:00:00.000Z', + 'name': 'Naturally Sourced Vitamin E', + 'quantity': 100, + 'quantityUnits': 'caps', + 'servingUnit': 'mg', + 'numUnitsInServing': 134, + 'numBottles': 2}] +""" +import datetime as dt +import json +import toml +from typing_extensions import Annotated + +import typer + +from sup.main import load_ordered_supps, ORDERED_SUPPS_FP, load_config, SUPP_CONSUMPTION_FP, load_inventory, Supp + +app = typer.Typer() + + +class UnitMismatch(Exception): + pass + + +def get_qty_inventory(supp: Supp, inventory: dict) -> int: + inventory_order_date = dt.datetime.strptime(inventory['orderDate'][:10], "%Y-%m-%d").date() + num_days_since_bought = (dt.date.today() - inventory_order_date).days + if inventory['servingUnit'] != supp.units: + raise UnitMismatch(inventory, supp) + + return (inventory['quantity'] * inventory['numBottles']) - (supp * num_days_since_bought) + + +def get_num_winter_days_starting(num_days: int, starting: dt.date) -> int: + # dec 21 to mar 20 + winter_starts = dt.date(starting.year, 12, 21) + if starting <= winter_starts: + num_days_til_winter = (winter_starts - starting).days + if num_days_til_winter > num_days: + return 0 + return num_days - num_days_til_winter + + winter_ends = dt.date(starting.year, 3, 2) + if starting >= winter_ends: + winter_starts = dt.date(starting.year + 1, 12, 21) + num_days_til_winter = (winter_starts - starting).days + if num_days_til_winter > num_days: + return 0 + return num_days - num_days_til_winter + + winter_ends = dt.date(starting.year + 1, 3, 2) + if starting <= winter_ends: + num_days_til_winter_ends = (winter_ends - starting).days + if num_days_til_winter_ends > num_days: + return num_days + return num_days_til_winter_ends + raise Exception + + +@app.command() +def status(): + """check if there's enough inventory for the next fill-up; if not, what to order?""" + config = load_config() + num_days_of_inventory_needed = config['FILL_EVERY_X_DAYS'] + next_fill_date = config['LAST_FILL_DATE'] + dt.timedelta(num_days_of_inventory_needed) + inventory = load_inventory() + needs = [] + for sup in config['supps']: + sup_inst = Supp(**sup) + if sup_inst.winter_only: + num_days_of_inventory_needed = get_num_winter_days_starting(num_days_of_inventory_needed, next_fill_date) + qty_needed = sup_inst * num_days_of_inventory_needed + try: + inv = inventory[sup_inst.name] + except KeyError: + qty_of_inventory = 0 + else: + qty_of_inventory = get_qty_inventory(sup_inst, inv) + net_need = qty_needed - qty_of_inventory + if net_need > 0: + needs.append((sup_inst.name, net_need, sup_inst.units)) + if needs: + print(f"The next fill-up is on {next_fill_date}, and you won't have enough of:") + for name, quant, units in needs: + print(f"{name} (need {quant} {units})") + + +@app.command() +def fill(): + """reset 'next fill' clock""" + config = load_config() + today = dt.date.today() + config["LAST_FILL_DATE"] = today.strftime("%Y-%m-%d") + # TODO: make sure this toml library doesn't add quotes to this entry, it + # makes it so when reading it doesn't get parsed to a date + fill_every_x_days = config['FILL_EVERY_X_DAYS'] + save_config(config) + print(f"Okay, next fill-up set to {today + dt.timedelta(days=fill_every_x_days)} (configured in `sup.toml`::FILL_EVERY_X_DAYS)") + + +@app.command() +def add( + name: Annotated[str, typer.Option(prompt=True)], + quantity: Annotated[int, typer.Option(prompt=True)], + serving_quantity: Annotated[int, typer.Option(prompt=True)], + serving_unit: Annotated[str, typer.Option(prompt=True)] = "mg", + quantity_unit: Annotated[str, typer.Option(prompt=True)] = "caps", + date: Annotated[dt.datetime, typer.Option(help="(today)", prompt=True)] + | None = None, + number_of_bottles: Annotated[int, typer.Option(prompt=True)] = 1, +) -> None: + """add to inventory""" + if date is None: + date = dt.datetime.now().date() # type: ignore + else: + date = date.date() # type: ignore + ordered_supps = load_ordered_supps() + order_dict = dict( + name=name, + quantity=quantity, + numUnitsInServing=serving_quantity, + servingUnit=serving_unit, + quantityUnit=quantity_unit, + orderDate=date.strftime("%Y-%m-%d"), # type: ignore + numBottles=number_of_bottles, + ) + ordered_supps.append(order_dict) + save_ordered_supps(ordered_supps) + print(f"added {order_dict} to {ORDERED_SUPPS_FP}") + + +def save_ordered_supps(ordered_supps: list[dict]) -> None: + ORDERED_SUPPS_FP.write_text(json.dumps(ordered_supps, indent=2)) + + +def save_config(config: dict) -> None: + SUPP_CONSUMPTION_FP.write_text(toml.dumps(config)) diff --git a/src/sup/main.py b/src/sup/main.py new file mode 100644 index 0000000..b7a6509 --- /dev/null +++ b/src/sup/main.py @@ -0,0 +1,90 @@ +""" +TODO: + special case: Liquid D-3 & MK-7 -- has d-3 and k-mk7 + special case: K Complex has K1, MK-4 and MK-7 in it +""" +from dataclasses import dataclass +import json +import pathlib as pl +import tomllib +import typing as t + + +ORDERED_SUPPS_FP = pl.Path("supps.json") +SUPP_CONSUMPTION_FP = pl.Path("supps.toml") + + +def load_config(): + return tomllib.loads(SUPP_CONSUMPTION_FP.read_text()) + + + + +def load_ordered_supps() -> list[dict]: + return json.loads(ORDERED_SUPPS_FP.read_text()) + +CONFIG = load_config() +ALIASES = CONFIG["product_aliases"] + +ordered_supps = load_ordered_supps() + +for i in CONFIG["supps"]: + for ordered_supp in ordered_supps: + if i["name"].lower() in ordered_supp['name'].lower(): + ALIASES[ordered_supp['name']] = i["name"] + +ALIASES_REV = {v: k for k, v in ALIASES.items()} + +def load_inventory(): + return { ALIASES[s['name']]: s for s in load_ordered_supps() if s['name'] not in CONFIG['discontinued'] } + + +class Missing(Exception): + pass + + +def validate_matches() -> None: + missing = [] + + ordered_supp_names_lower = [i["name"].lower() for i in load_ordered_supps()] + for i in CONFIG["supps"]: + if ( + not any( + i["name"].lower() in ordered_supp_name + for ordered_supp_name in ordered_supp_names_lower + ) + and i["name"].lower() not in ALIASES_REV + ): + missing.append(i["name"]) + + if missing: + raise Missing(", ".join(missing)) + + +@dataclass +class Supp: + name: str + morning: int | float = 0 + lunch: int | float = 0 + dinner: int | float = 0 + bedtime: int | float = 0 + days_per_week: int = 7 + units: t.Literal["caps", "mg", "g", "ml", "mcg", "iu"] = "mg" + winter_only: bool = False + + def __mul__(self, other: int) -> float: + return self.quantity_per_day * other + + @property + def quantity_per_day(self) -> float: + return sum([self.morning, self.lunch, self.dinner, self.bedtime]) * 7 / self.days_per_week + + +def main(): + missing_names = validate_matches() + if missing_names: + print(missing_names) + + +if __name__ == "__main__": + main() diff --git a/supps.json b/supps.json index 3d3c554..437029c 100644 --- a/supps.json +++ b/supps.json @@ -13,8 +13,8 @@ "name": "Super K", "quantity": 90, "quantityUnits": "caps", - "servingUnit": "caps", - "numUnitsInServing": 1, + "servingUnit": "mcg", + "numUnitsInServing": 1500, "numBottles": 1 }, { @@ -58,8 +58,8 @@ "name": "Glycine", "quantity": 250, "quantityUnits": "caps", - "servingUnit": "caps", - "numUnitsInServing": 1, + "servingUnit": "mg", + "numUnitsInServing": 350, "numBottles": 1 }, { @@ -83,11 +83,11 @@ { "orderDate": "2024-01-23T23:00:00.000Z", "name": "Liquid D-3 & MK-7", - "quantity": 30, - "quantityUnits": "ml", - "servingUnit": "ml", - "numUnitsInServing": 0.368, - "numBottles": 4 + "quantity": 81.5, + "quantityUnits": "iu", + "servingUnit": "iu", + "numUnitsInServing": 5000, + "numBottles": 1 }, { "orderDate": "2024-01-23T23:00:00.000Z", @@ -112,7 +112,7 @@ "name": "Magnesium Taurate", "quantity": 180, "quantityUnits": "caps", - "servingUnit": "cap", + "servingUnit": "caps", "numUnitsInServing": 1, "numBottles": 1 }, @@ -184,8 +184,8 @@ "name": "Probiotic-10", "quantity": 50, "quantityUnits": "caps", - "servingUnit": "Billion", - "numUnitsInServing": 25, + "servingUnit": "caps", + "numUnitsInServing": 1, "numBottles": 1 }, { @@ -193,8 +193,8 @@ "name": "Iron Bisglycinate", "quantity": 60, "quantityUnits": "caps", - "servingUnit": "cap", - "numUnitsInServing": 1, + "servingUnit": "mg", + "numUnitsInServing": 25, "numBottles": 1 }, { @@ -211,7 +211,7 @@ "name": "Extend-Release Magnesium", "quantity": 60, "quantityUnits": "caps", - "servingUnit": "cap", + "servingUnit": "caps", "numUnitsInServing": 1, "numBottles": 1 }, @@ -231,7 +231,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 125, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -240,16 +240,16 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 600, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", "name": "Crucera-SGS", "quantity": 60, "quantityUnits": "caps", - "servingUnit": "cap", + "servingUnit": "caps", "numUnitsInServing": 1, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -258,7 +258,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 1000, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -267,7 +267,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 300, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -276,7 +276,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 12, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -285,7 +285,7 @@ "quantityUnits": "caps", "servingUnit": "mcg", "numUnitsInServing": 1000, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -294,7 +294,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 3, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -303,7 +303,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 1000, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -312,16 +312,16 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 2250, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", "name": "Lutein & Zeaxanthin", "quantity": 60, "quantityUnits": "caps", - "servingUnit": "cap", - "numUnitsInServing": 1, - "numBottles": 2 + "servingUnit": "mg", + "numUnitsInServing": 20, + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -330,7 +330,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 15, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -339,7 +339,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 300, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -348,7 +348,7 @@ "quantityUnits": "g", "servingUnit": "g", "numUnitsInServing": 5, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -357,7 +357,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 10, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -366,7 +366,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 500, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -375,7 +375,7 @@ "quantityUnits": "caps", "servingUnit": "mcg", "numUnitsInServing": 100, - "numBottles": 2 + "numBottles": 4 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -384,7 +384,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 1000, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -393,25 +393,25 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 500, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", "name": "Lithium Orotate Drops", "quantity": 59, "quantityUnits": "ml", - "servingUnit": "ml", - "numUnitsInServing": 0.25, - "numBottles": 2 + "servingUnit": "mg", + "numUnitsInServing": 1, + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", "name": "Liquid Iodine Plus", "quantity": 59, "quantityUnits": "ml", - "servingUnit": "ml", - "numUnitsInServing": 0.126, - "numBottles": 2 + "servingUnit": "mcg", + "numUnitsInServing": 125, + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -420,7 +420,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 100, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -429,7 +429,7 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 81, - "numBottles": 2 + "numBottles": 1 }, { "orderDate": "2024-01-04T23:00:00.000Z", @@ -438,6 +438,6 @@ "quantityUnits": "caps", "servingUnit": "mg", "numUnitsInServing": 134, - "numBottles": 2 + "numBottles": 1 } ] diff --git a/supps.toml b/supps.toml index 15b09a5..03ecbf7 100644 --- a/supps.toml +++ b/supps.toml @@ -1,207 +1,217 @@ -[[ supps ]] +discontinued = ['NAD+ Cell Regenerator', 'Fisetin with Novusetin', 'Fisetin Novusetin', 'Collagen'] +FILL_EVERY_X_DAYS = 30 +LAST_FILL_DATE = 2024-01-16 + +[[supps]] name = "ashwagandha" bedtime = 600 -[[ supps ]] +[[supps]] name = "aspirin" lunch = 86 -per_week = 3 +days_per_week = 3 -[[ supps ]] +[[supps]] name = "astaxanthin" morning = 12 -[[ supps ]] +[[supps]] name = "b complex" morning = 0.5 -per_week = 2 +days_per_week = 2 units = "caps" -[[ supps ]] +[[supps]] name = "b-12" lunch = 1000 units = "mcg" -per_week = 1 +days_per_week = 1 -[[ supps ]] +[[supps]] name = "boron" morning = 2 -[[ supps ]] +[[supps]] name = "broccomax" -morning = 17.5 -dinner = 17.5 +morning = 1 +dinner = 1 +units = "caps" -[[ supps ]] +[[supps]] name = "vitamin c" morning = 500 -[[ supps ]] +[[supps]] name = "creatine" lunch = 2.5 +days_per_week = 4 units = "g" -per_week = 4 -[[ supps ]] +[[supps]] name = "Ca-AKG" -morning = 1 -dinner = 1 -units = "g" +morning = 1000 +dinner = 1000 -[[ supps ]] +[[supps]] name = "coq10" morning = 100 lunch = 100 dinner = 100 -[[ supps ]] +[[supps]] name = "d-3" morning = 5000 units = "iu" winter_only = true -[[ supps ]] +[[supps]] name = "DHEA" morning = 25 -[[ supps ]] +[[supps]] name = "vitamin e" -morning = 200 -per_week = 3 -units = "iu" +morning = 67 +days_per_week = 3 -[[ supps ]] +[[supps]] name = "EPA/DHA" morning = 2 dinner = 1 units = "caps" -[[ supps ]] +[[supps]] name = "garlic" -morning = 1.2 -dinner = 1.2 -units = "g" +morning = 1200 +dinner = 1200 -[[ supps ]] +[[supps]] name = "genistein" -morning = 1 -units = "caps" +morning = 125 -[[ supps ]] +[[supps]] name = "ginger root" -morning = 2.2 -dinner = 2.2 -units = "g" +morning = 2200 +dinner = 2200 -[[ supps ]] +[[supps]] name = "glucosamine sulfate" morning = 1500 dinner = 1500 -[[ supps ]] +[[supps]] name = "glycine" -morning = 2 -units = "g" +morning = 2000 -[[ supps ]] +[[supps]] name = "hyaluronic acid" morning = 300 -[[ supps ]] +[[supps]] name = "iodine" morning = 125 units = "mcg" -[[ supps ]] +[[supps]] name = "iron" morning = 10 -[[ supps ]] +[[supps]] name = "k1" -morning = 1.5 +morning = 1500 +units="mcg" -[[ supps ]] +[[supps]] name = "k2-mk7" morning = 600 units = "mcg" -[[ supps ]] +[[supps]] name = "k2-mk4" morning = 5 -[[ supps ]] +[[supps]] name = "l-lysine" -morning = 1 -dinner = 1 -units = "g" +morning = 1000 +dinner = 1000 -[[ supps ]] +[[supps]] name = "l-tyrosine" dinner = 500 -[[ supps ]] +[[supps]] name = "lithium orotate" morning = 1 -[[ supps ]] +[[supps]] name = "lycopene" morning = 10 -[[ supps ]] +[[supps]] name = "magnesium slow release" morning = 1 units = "caps" -[[ supps ]] +[[supps]] name = "magnesium taurate" bedtime = 125 -[[ supps ]] +[[supps]] name = "magnesium glycinate" bedtime = 350 -[[ supps ]] +[[supps]] name = "magtein" -bedtime = 2 -units = "g" +bedtime = 2000 -[[ supps ]] +[[supps]] name = "methyl folate" -morning = 1 -units = "caps" +morning = 1700 +units = "mcg" -[[ supps ]] +[[supps]] name = "NAC" morning = 1800 dinner = 1800 -[[ supps ]] +[[supps]] name = "pqq" -morning = 1 -units = "caps" +morning = 20 -[[ supps ]] +[[supps]] name = "probiotic" morning = 1 units = "caps" -[[ supps ]] +[[supps]] name = "taurine" -morning = 2 -dinner = 1 -units = "g" +morning = 2000 +dinner = 1000 -[[ supps ]] +[[supps]] name = "turmeric" -morning = 1 -dinner = 1 -units = "g" +morning = 1000 +dinner = 1000 -[[ supps ]] +[[supps]] name = "zeaxanthin" morning = 20 -per_week = 3 +days_per_week = 3 -[[ supps ]] +[[supps]] name = "zinc" morning = 15 + +[product_aliases] +"Naturally Sourced Vitamin E" = "vitamin e" +"Natural Vitamin K2 MK-7 with MenaQ7" = "k2-mk7" +"Liquid D-3 & MK-7" = "d-3" +"Calcium AKG Longevity" = "ca-akg" +"MK-7 Vitamin K-2" = "k2-mk7" +"Optimized Folate" = "methyl folate" +"Extend-Release Magnesium" = "magnesium slow release" +Crucera-SGS = "broccomax" +B-50 = "b complex" +"Vitamin K2, MK-4 (Menatetrenone)" = "k2-mk4" +"Super K" = "k1" +"Ultimate Omega, Lemon" = "epa/dha" +