# -*- coding: utf-8 -*-
#
# SPDX-FileCopyrightText: © 2013 The glucometerutils Authors
# SPDX-License-Identifier: MIT
"""Common routines for data in glucometers."""
import datetime
import enum
import textwrap
from collections.abc import Sequence
from typing import Any, Optional, Union
import attr
class Unit(enum.Enum):
MG_DL = "mg/dL"
MMOL_L = "mmol/L"
# Constants for meal information
class Meal(enum.Enum):
NONE = ""
BEFORE = "Before Meal"
AFTER = "After Meal"
# Constants for measure method
class MeasurementMethod(enum.Enum):
BLOOD_SAMPLE = "blood sample"
CGM = "CGM" # Continuous Glucose Monitoring
TIME = "time"
def convert_glucose_unit(value: float, from_unit: Unit, to_unit: Unit) -> float:
"""Convert the given value of glucose level between units.
Args:
value: The value of glucose in the current unit
from_unit: The unit value is currently expressed in
to_unit: The unit to conver the value to: the other if empty.
Returns:
The converted representation of the blood glucose level.
"""
from_unit = Unit(from_unit)
to_unit = Unit(to_unit)
if from_unit == to_unit:
return value
if from_unit == Unit.MG_DL:
return round(value / 18.0, 2)
return round(value * 18.0, 1)
@attr.s(auto_attribs=True)
class GlucoseReading:
timestamp: datetime.datetime
value: float
meal: Meal = attr.ib(default=Meal.NONE, validator=attr.validators.in_(Meal))
comment: str = ""
measure_method: MeasurementMethod = attr.ib(
default=MeasurementMethod.BLOOD_SAMPLE,
validator=attr.validators.in_(MeasurementMethod),
)
extra_data: dict[str, Any] = attr.Factory(dict)
def get_value_as(self, to_unit: Unit) -> float:
"""Returns the reading value as the given unit.
Args:
to_unit: The unit to return the value to.
"""
return convert_glucose_unit(self.value, Unit.MG_DL, to_unit)
def as_csv(self, unit: Unit) -> str:
"""Returns the reading as a formatted comma-separated value string."""
return '"%s","%.2f","%s","%s","%s"' % (
self.timestamp,
self.get_value_as(unit),
self.meal.value,
self.measure_method.value,
self.comment,
)
@attr.s(auto_attribs=True)
class KetoneReading:
timestamp: datetime.datetime
value: float
comment: str = ""
measure_method: MeasurementMethod = attr.ib(
default=MeasurementMethod.BLOOD_SAMPLE,
validator=attr.validators.in_({MeasurementMethod.BLOOD_SAMPLE}),
)
extra_data: dict[str, Any] = attr.Factory(dict)
def as_csv(self, unit: Unit) -> str:
"""Returns the reading as a formatted comma-separated value string."""
del unit # Unused for Ketone readings.
return '"%s","%.2f","","%s","%s"' % (
self.timestamp,
self.value,
self.measure_method.value,
self.comment,
)
@attr.s(auto_attribs=True)
class TimeAdjustment:
timestamp: datetime.datetime
old_timestamp: datetime.datetime
measure_method: MeasurementMethod = attr.ib(
default=MeasurementMethod.TIME, validator=attr.validators.in_(MeasurementMethod)
)
extra_data: dict[str, Any] = attr.Factory(dict)
def as_csv(self, unit: Unit) -> str:
del unit
return '"%s","","","%s","%s"' % (
self.timestamp,
self.measure_method.value,
self.old_timestamp,
)
AnyReading = Union[GlucoseReading, KetoneReading, TimeAdjustment]
@attr.s(auto_attribs=True)
class MeterInfo:
"""General information about the meter.
Attributes:
model: Human readable model name, chosen by the driver.
serial_number: Serial number identified for the reader (or N/A if not
available in the protocol.)
version_info: List of strings with any version information available about
the device. It can include hardware and software version.
native_unit: One of the Unit values to identify the meter native unit.
"""
model: str
serial_number: str = "N/A"
version_info: Sequence[str] = ()
native_unit: Unit = attr.ib(default=Unit.MG_DL, validator=attr.validators.in_(Unit))
patient_name: Optional[str] = None
def __str__(self) -> str:
version_information_string = "N/A"
if self.version_info:
version_information_string = "\n ".join(
self.version_info
).strip()
base_output = textwrap.dedent(
f"""\
{self.model}
Serial Number: {self.serial_number}
Version Information:
{version_information_string}
Native Unit: {self.native_unit.value}
"""
)
if self.patient_name is not None:
base_output += f"Patient Name: {self.patient_name}\n"
return base_output