140 lines
3.3 KiB
Python
Executable file
140 lines
3.3 KiB
Python
Executable file
"""
|
|
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
|
|
|
|
|
|
def main():
|
|
print(data())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|