#!/usr/bin/env python3 """ health-poller: pull vitals from consumer health devices into Inou. Wraps Home Assistant integrations — never reimplements vendor APIs. Usage: python -m poller.main --config config.yaml """ import argparse import asyncio import logging from poller.config import load_config from poller.dedup import Dedup from poller.sink import Sink from poller.sources.renpho import RenphoSource logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) log = logging.getLogger("health-poller") SOURCE_CLASSES = { "renpho": RenphoSource, } def make_source(cfg: dict): cls = SOURCE_CLASSES.get(cfg["type"]) if not cls: raise ValueError(f"unknown source type: {cfg['type']}") if cfg["type"] == "renpho": return cls(email=cfg["email"], password=cfg["password"], user_id=cfg.get("user_id")) raise ValueError(f"no constructor for source type: {cfg['type']}") async def poll_source(src_cfg: dict, dedup: Dedup, sink: Sink): source = make_source(src_cfg) dossier_id = src_cfg.get("dossier_id", "") readings = await source.fetch() new = dedup.filter_new(readings) if new: sink.push(dossier_id, new) dedup.mark_seen(new) log.info(f"{src_cfg['type']}: pushed {len(new)} new readings") else: log.info(f"{src_cfg['type']}: no new readings") async def main(): parser = argparse.ArgumentParser(description="Inou health data poller") parser.add_argument("--config", default="config.yaml", help="config file path") args = parser.parse_args() cfg = load_config(args.config) dedup = Dedup() sink = Sink(cfg["inou"]["api_url"], cfg["inou"].get("api_key", "")) for src_cfg in cfg["sources"]: try: await poll_source(src_cfg, dedup, sink) except Exception: log.exception(f"error polling {src_cfg['type']}") if __name__ == "__main__": asyncio.run(main())