Skip to content

transformation_service

django_spire.metric.visual.services.transformation_service

VisualTransformationService

Bases: BaseDjangoModelService['Visual']

Source code in django_spire/contrib/constructor/constructor.py
def __init__(self, obj: Any = None):
    self._obj_type_name: str = str(next(iter(self.__class__.__annotations__.values()))).split(
        '.'
    )[-1]

    if obj is None:
        return

    self._obj_mro_type_names = [cls.__name__ for cls in obj.__class__.__mro__]

    if self._obj_type_name not in self._obj_mro_type_names:
        message = f'{self.__class__.__name__} was instantiated with obj type "{obj.__class__.__name__}" and failed as it was expecting "{self._obj_type_name}".'
        raise ConstructorError(message)

    self._obj_type: type[TypeAny] = obj.__class__

    if self._obj_type is None or self._obj_type is ...:
        message = f'{self.__class__.__name__} top class attribute must have an annotated type.'
        raise ConstructorError(message)

    self.obj: TypeAny = obj

    if ABC not in self.__class__.__bases__:
        if not self._obj_is_valid:
            message = f'{self._obj_type_name} failed to validate on {self.__class__.__name__}'
            raise ConstructorError(message)

    self.__post_init__()

obj instance-attribute

date_range

Source code in django_spire/metric/visual/services/transformation_service.py
def date_range(self, value_date: date | None = None) -> tuple[date, date]:
    value_date = value_date or self.obj.date

    interval = self.obj.statistic.interval if self.obj.statistic_id else None

    if not interval:
        return value_date, value_date

    return interval_range(interval, value_date)

current_value

Source code in django_spire/metric/visual/services/transformation_service.py
def current_value(self, value_date: date | None = None) -> Decimal:
    if not self.obj.statistic_id:
        return Decimal(0)

    start_date, end_date = self.date_range(value_date)

    values = self.obj.statistic.values.date_range(start_date, end_date)

    if self.obj.reference:
        values = values.for_reference(self.obj.reference)

    return values.total()

current_condition

Source code in django_spire/metric/visual/services/transformation_service.py
def current_condition(self, value_date: date | None = None) -> VisualCondition | None:
    value = self.current_value(value_date)

    for condition in self.obj.conditions.all():
        if condition.matches(value):
            return condition

    return None

series_data

Source code in django_spire/metric/visual/services/transformation_service.py
def series_data(self, value_date: date | None = None) -> list[dict]:
    if not self.obj.statistic_id:
        return []

    start_date, end_date = self.date_range(value_date)

    values = self.obj.statistic.values.date_range(start_date, end_date)

    if self.obj.reference:
        values = values.for_reference(self.obj.reference)

    return [
        {'timestamp': value.timestamp, 'value': value.value}
        for value in values.order_by('timestamp')
    ]

series_breakdown

Source code in django_spire/metric/visual/services/transformation_service.py
def series_breakdown(self, value_date: date | None = None) -> list[dict]:
    if not self.obj.statistic_id:
        return []

    start_date, end_date = self.date_range(value_date)

    values = self.obj.statistic.values.date_range(start_date, end_date)

    if self.obj.reference:
        values = values.for_reference(self.obj.reference)

    totals: dict[str, Decimal] = {}

    for value in values:
        reference = value.reference or 'Unassigned'
        totals[reference] = totals.get(reference, Decimal(0)) + value.value

    return [
        {'name': reference, 'value': float(total)}
        for reference, total in sorted(totals.items())
    ]

gauge_max

Source code in django_spire/metric/visual/services/transformation_service.py
def gauge_max(self) -> int:
    ceiling = Decimal(0)

    for condition in self.obj.conditions.all():
        upper = condition.target + condition.tolerance
        ceiling = max(ceiling, upper)

    if ceiling <= 0:
        ceiling = self.current_value() * Decimal(2)

    if ceiling <= 0:
        ceiling = Decimal(100)

    return int(ceiling)

chart

Source code in django_spire/metric/visual/services/transformation_service.py
def chart(self) -> Any | None:
    from django_spire.metric.visual.charts import VISUAL_CHART_CLASSES  # noqa: PLC0415

    chart_class = VISUAL_CHART_CLASSES.get(self.obj.kind)

    if chart_class is None:
        return None

    return chart_class(params={'visual_pk': self.obj.pk})

VisualConditionTransformationService

Bases: BaseDjangoModelService['VisualCondition']

Source code in django_spire/contrib/constructor/constructor.py
def __init__(self, obj: Any = None):
    self._obj_type_name: str = str(next(iter(self.__class__.__annotations__.values()))).split(
        '.'
    )[-1]

    if obj is None:
        return

    self._obj_mro_type_names = [cls.__name__ for cls in obj.__class__.__mro__]

    if self._obj_type_name not in self._obj_mro_type_names:
        message = f'{self.__class__.__name__} was instantiated with obj type "{obj.__class__.__name__}" and failed as it was expecting "{self._obj_type_name}".'
        raise ConstructorError(message)

    self._obj_type: type[TypeAny] = obj.__class__

    if self._obj_type is None or self._obj_type is ...:
        message = f'{self.__class__.__name__} top class attribute must have an annotated type.'
        raise ConstructorError(message)

    self.obj: TypeAny = obj

    if ABC not in self.__class__.__bases__:
        if not self._obj_is_valid:
            message = f'{self._obj_type_name} failed to validate on {self.__class__.__name__}'
            raise ConstructorError(message)

    self.__post_init__()

obj instance-attribute

matches

Source code in django_spire/metric/visual/services/transformation_service.py
def matches(self, value: Decimal) -> bool:
    value = Decimal(value)

    comparisons: dict[str, bool] = {
        VisualConditionOperatorChoices.GT: value > self.obj.target,
        VisualConditionOperatorChoices.GTE: value >= self.obj.target,
        VisualConditionOperatorChoices.LT: value < self.obj.target,
        VisualConditionOperatorChoices.LTE: value <= self.obj.target,
        VisualConditionOperatorChoices.EQ: value == self.obj.target,
        VisualConditionOperatorChoices.BETWEEN: abs(value - self.obj.target)
        <= self.obj.tolerance,
    }

    return comparisons.get(self.obj.operator, False)