HPI/my/body/blood.py

132 lines
3.3 KiB
Python

"""
Blood tracking (manual org-mode entries)
"""
from datetime import datetime
from typing import Iterable, NamedTuple, Optional
from ..core.error import Res
from ..core.orgmode import parse_org_datetime, one_table
import pandas as pd # type: ignore
import orgparse
from my.config import blood as config
class Entry(NamedTuple):
dt: datetime
ketones : Optional[float]=None
glucose : Optional[float]=None
vitamin_d : Optional[float]=None
vitamin_b12 : Optional[float]=None
hdl : Optional[float]=None
ldl : Optional[float]=None
triglycerides: Optional[float]=None
source : Optional[str]=None
extra : Optional[str]=None
Result = Res[Entry]
def try_float(s: str) -> Optional[float]:
l = s.split()
if len(l) == 0:
return None
# meh. this is to strip away HI/LO? Maybe need extract_float instead or something
x = l[0].strip()
if len(x) == 0:
return None
return float(x)
def glucose_ketones_data() -> Iterable[Result]:
o = orgparse.load(config.blood_log)
[n] = [x for x in o if x.heading == 'glucose/ketones']
tbl = one_table(n)
# todo some sort of sql-like interface for org tables might be ideal?
for l in tbl.as_dicts:
kets = l['ket']
glus = l['glu']
extra = l['notes']
dt = parse_org_datetime(l['datetime'])
try:
assert isinstance(dt, datetime)
ket = try_float(kets)
glu = try_float(glus)
except Exception as e:
ex = RuntimeError(f'While parsing {l}')
ex.__cause__ = e
yield ex
else:
yield Entry(
dt=dt,
ketones=ket,
glucose=glu,
extra=extra,
)
def blood_tests_data() -> Iterable[Result]:
o = orgparse.load(config.blood_log)
[n] = [x for x in o if x.heading == 'blood tests']
tbl = one_table(n)
for d in tbl.as_dicts:
try:
dt = parse_org_datetime(d['datetime'])
assert isinstance(dt, datetime), dt
F = lambda n: try_float(d[n])
yield Entry(
dt=dt,
vitamin_d =F('VD nm/L'),
vitamin_b12 =F('B12 pm/L'),
hdl =F('HDL mm/L'),
ldl =F('LDL mm/L'),
triglycerides=F('Trig mm/L'),
source =d['source'],
extra =d['notes'],
)
except Exception as e:
ex = RuntimeError(f'While parsing {d}')
ex.__cause__ = e
yield ex
def data() -> Iterable[Result]:
from itertools import chain
from ..core.error import sort_res_by
datas = chain(glucose_ketones_data(), blood_tests_data())
return sort_res_by(datas, key=lambda e: e.dt)
def dataframe() -> pd.DataFrame:
rows = []
for x in data():
if isinstance(x, Exception):
# todo use some core helper? this is a pretty common operation
d = {'error': str(x)}
else:
d = x._asdict()
rows.append(d)
return pd.DataFrame(rows)
def stats():
from ..core import stat
return stat(data)
def test():
print(dataframe())
assert len(dataframe()) > 10