use cachew
This commit is contained in:
parent
2ba0ad6f2b
commit
8c5bdf0603
1 changed files with 63 additions and 35 deletions
|
@ -1,20 +1,25 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from datetime import datetime, time
|
import json
|
||||||
from pathlib import Path
|
|
||||||
from functools import lru_cache
|
|
||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict as odict
|
from collections import OrderedDict as odict
|
||||||
from datetime import timedelta, datetime, date
|
from dataclasses import dataclass
|
||||||
from typing import List, Dict, Iterator, NamedTuple
|
from datetime import date, datetime, time, timedelta
|
||||||
import json
|
from functools import lru_cache
|
||||||
import pytz
|
from pathlib import Path
|
||||||
|
from typing import Dict, Iterator, List, NamedTuple
|
||||||
|
|
||||||
from kython import cproperty, timed, group_by_key
|
import kython
|
||||||
|
import pytz
|
||||||
|
from kython import cproperty, group_by_key
|
||||||
|
|
||||||
|
from cachew import cachew
|
||||||
|
|
||||||
|
|
||||||
def get_logger():
|
def get_logger():
|
||||||
return logging.getLogger('emfit-provider')
|
return logging.getLogger('emfit-provider')
|
||||||
|
|
||||||
|
timed = lambda f: kython.timed(f, logger=get_logger())
|
||||||
|
|
||||||
def hhmm(minutes):
|
def hhmm(minutes):
|
||||||
return '{:02d}:{:02d}'.format(*divmod(minutes, 60))
|
return '{:02d}:{:02d}'.format(*divmod(minutes, 60))
|
||||||
|
|
||||||
|
@ -29,8 +34,9 @@ EXCLUDED = [
|
||||||
|
|
||||||
AWAKE = 4
|
AWAKE = 4
|
||||||
|
|
||||||
# TODO use tz provider for that? although emfit is always in london...
|
Sid = str
|
||||||
|
|
||||||
|
# TODO FIXME use tz provider for that? although emfit is always in london...
|
||||||
_TZ = pytz.timezone('Europe/London')
|
_TZ = pytz.timezone('Europe/London')
|
||||||
|
|
||||||
def fromts(ts) -> datetime:
|
def fromts(ts) -> datetime:
|
||||||
|
@ -38,7 +44,23 @@ def fromts(ts) -> datetime:
|
||||||
return _TZ.localize(dt)
|
return _TZ.localize(dt)
|
||||||
|
|
||||||
|
|
||||||
class Emfit:
|
class Mixin:
|
||||||
|
@property
|
||||||
|
# ok, I guess that's reasonable way of defining sleep date
|
||||||
|
def date(self):
|
||||||
|
return self.end.date()
|
||||||
|
|
||||||
|
@cproperty
|
||||||
|
def time_in_bed(self):
|
||||||
|
return int((self.sleep_end - self.sleep_start).total_seconds()) // 60
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recovery(self):
|
||||||
|
return self.hrv_morning - self.hrv_evening
|
||||||
|
|
||||||
|
|
||||||
|
# TODO def use multiple threads for that..
|
||||||
|
class EmfitOld(Mixin):
|
||||||
def __init__(self, sid: str, jj):
|
def __init__(self, sid: str, jj):
|
||||||
self.sid = sid
|
self.sid = sid
|
||||||
self.jj = jj
|
self.jj = jj
|
||||||
|
@ -54,11 +76,6 @@ class Emfit:
|
||||||
def hrv_evening(self):
|
def hrv_evening(self):
|
||||||
return self.jj['hrv_rmssd_evening']
|
return self.jj['hrv_rmssd_evening']
|
||||||
|
|
||||||
# ok, I guess that's reasonable way of defining sleep date
|
|
||||||
@property
|
|
||||||
def date(self):
|
|
||||||
return self.end.date()
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Bed time, not necessarily sleep
|
Bed time, not necessarily sleep
|
||||||
"""
|
"""
|
||||||
|
@ -104,10 +121,6 @@ class Emfit:
|
||||||
# 'sleep_epoch_datapoints'
|
# 'sleep_epoch_datapoints'
|
||||||
# [[timestamp, number]]
|
# [[timestamp, number]]
|
||||||
|
|
||||||
@cproperty
|
|
||||||
def time_in_bed(self):
|
|
||||||
return int((self.sleep_end - self.sleep_start).total_seconds()) // 60
|
|
||||||
|
|
||||||
# so it's actual sleep, without awake
|
# so it's actual sleep, without awake
|
||||||
# ok, so I need time_asleep
|
# ok, so I need time_asleep
|
||||||
@property
|
@property
|
||||||
|
@ -122,10 +135,6 @@ class Emfit:
|
||||||
def hrv_hf(self):
|
def hrv_hf(self):
|
||||||
return self.jj['hrv_hf']
|
return self.jj['hrv_hf']
|
||||||
|
|
||||||
@property
|
|
||||||
def recovery(self):
|
|
||||||
return self.hrv_morning - self.hrv_evening
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def summary(self):
|
def summary(self):
|
||||||
return f"""in bed for {hhmm(self.time_in_bed)}
|
return f"""in bed for {hhmm(self.time_in_bed)}
|
||||||
|
@ -252,19 +261,43 @@ recovery: {self.recovery:3.0f}
|
||||||
return covered / expected * 100
|
return covered / expected * 100
|
||||||
|
|
||||||
|
|
||||||
|
# right, so dataclass is better because you can use mixins
|
||||||
|
@dataclass(eq=True, frozen=True)
|
||||||
|
class Emfit(Mixin):
|
||||||
|
sid: Sid
|
||||||
|
hrv_morning: float
|
||||||
|
hrv_evening: float
|
||||||
|
start: datetime
|
||||||
|
end : datetime
|
||||||
|
sleep_start: datetime
|
||||||
|
sleep_end : datetime
|
||||||
|
sleep_hr_coverage: float
|
||||||
|
measured_hr_avg: float
|
||||||
|
|
||||||
@lru_cache(1000) # TODO hmm. should I configure it dynamically???
|
@classmethod
|
||||||
def get_emfit(sid: str, f: Path) -> Emfit:
|
def make(cls, em) -> Iterator['Emfit']:
|
||||||
return Emfit(sid=sid, jj=json.loads(f.read_text()))
|
# TODO FIXME res?
|
||||||
|
logger = get_logger()
|
||||||
|
if em.epochs is None:
|
||||||
|
logger.error('%s (on %s) got None in epochs! ignoring', em.sid, em.date)
|
||||||
|
return
|
||||||
|
|
||||||
|
yield cls(**{
|
||||||
|
k: getattr(em, k) for k in Emfit.__annotations__
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO very nice!
|
||||||
|
@cachew
|
||||||
def iter_datas() -> Iterator[Emfit]:
|
def iter_datas() -> Iterator[Emfit]:
|
||||||
for f in PATH.glob('*.json'):
|
for f in sorted(PATH.glob('*.json')):
|
||||||
sid = f.stem
|
sid = f.stem
|
||||||
if sid in EXCLUDED:
|
if sid in EXCLUDED:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
yield get_emfit(sid, f)
|
em = EmfitOld(sid=sid, jj=json.loads(f.read_text()))
|
||||||
|
yield from Emfit.make(em)
|
||||||
|
|
||||||
|
|
||||||
def get_datas() -> List[Emfit]:
|
def get_datas() -> List[Emfit]:
|
||||||
|
@ -282,11 +315,6 @@ def by_night() -> Dict[date, Emfit]:
|
||||||
logger.warning("multiple sleeps per night, not handled yet: %s", sleeps)
|
logger.warning("multiple sleeps per night, not handled yet: %s", sleeps)
|
||||||
continue
|
continue
|
||||||
[s] = sleeps
|
[s] = sleeps
|
||||||
|
|
||||||
if s.epochs is None:
|
|
||||||
logger.error('%s (on %s) got None in epochs! ignoring', s.sid, dd)
|
|
||||||
continue
|
|
||||||
|
|
||||||
res[s.date] = s
|
res[s.date] = s
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -319,10 +347,10 @@ def test_tz():
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
from kython.klogging import setup_logzero
|
||||||
|
setup_logzero(get_logger(), level=logging.DEBUG)
|
||||||
for k, v in by_night().items():
|
for k, v in by_night().items():
|
||||||
print(k, v.start, v.end)
|
print(k, v.start, v.end)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from kython.klogging import setup_logzero
|
|
||||||
setup_logzero(get_logger())
|
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Add table
Reference in a new issue