Files
aerbim-ht-monitor/backend/sitemanagement/models.py
2025-09-04 18:37:23 +03:00

151 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from decimal import Decimal
class Multiplexor(models.Model):
"""Устройство-мультиплексор"""
name = models.CharField(max_length=50, unique=True)
ip = models.GenericIPAddressField(null=True, blank=True)
subnet = models.GenericIPAddressField(null=True, blank=True)
gateway = models.GenericIPAddressField(null=True, blank=True)
sd_path = models.CharField(max_length=255, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Мультиплексор"
verbose_name_plural = "Мультиплексоры"
ordering = ["name"]
def __str__(self):
return self.name
class Channel(models.Model):
"""Физический канал мультиплексора"""
multiplexor = models.ForeignKey(Multiplexor, on_delete=models.CASCADE, related_name="channels")
number = models.PositiveSmallIntegerField() # CH-1 ... CH-14
# для "полканалов" можно использовать дробное значение (1, 1.5, 2, ...)
position = models.DecimalField(max_digits=4, decimal_places=1, default=Decimal("1.0"))
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ("multiplexor", "number", "position")
verbose_name = "Канал"
verbose_name_plural = "Каналы"
ordering = ["multiplexor", "number", "position"]
def __str__(self):
return f"{self.multiplexor.name} - CH{self.number} ({self.position})"
class SensorType(models.Model):
"""Тип датчика: GA, PE, GLE"""
code = models.CharField(max_length=10, unique=True) # GA, PE, GLE
name = models.CharField(max_length=100)
description = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# пороговые значения для всех сенсоров этого типа
min_value = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True)
max_value = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True)
class Meta:
verbose_name = "Тип сенсора"
verbose_name_plural = "Типы сенсоров"
ordering = ["name"]
def __str__(self):
return self.code
class SignalFormat(models.Model):
"""Формат сигнала и правило преобразования"""
sensor_type = models.ForeignKey(SensorType, on_delete=models.CASCADE, related_name="formats")
code = models.CharField(max_length=50) # например "4-20мА", "VW f<1600Hz", "NTC R>250Ohm"
unit = models.CharField(max_length=20, blank=True, null=True) # °C, мкм/м, мм и т.п.
conversion_rule = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ("sensor_type", "code")
verbose_name = "Формат сигнала"
verbose_name_plural = "Форматы сигналов"
ordering = ["sensor_type", "code"]
def __str__(self):
return f"{self.sensor_type.code} - {self.code}"
class Sensor(models.Model):
"""Конкретный датчик, установленный в канале"""
channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name="sensors")
sensor_type = models.ForeignKey(SensorType, on_delete=models.CASCADE)
signal_format = models.ForeignKey(SignalFormat, on_delete=models.SET_NULL, null=True, blank=True)
serial_number = models.CharField(max_length=50, blank=True, null=True) # CL 2106009
name = models.CharField(max_length=50, blank=True, null=True) # GA-1, HLE-1 и т.п.
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
math_formula = models.CharField(null=True, blank=True, max_length=255)
class Meta:
verbose_name = "Датчик"
verbose_name_plural = "Датчики"
ordering = ["channel", "sensor_type", "serial_number"]
def __str__(self):
return f"{self.name or self.sensor_type.code} ({self.serial_number})"
class Metric(models.Model):
"""Значения, которые приходят из CSV"""
timestamp = models.DateTimeField()
sensor = models.ForeignKey(Sensor, on_delete=models.CASCADE, related_name="metrics")
raw_value = models.CharField(max_length=50) # исходное значение из файла (например "11.964 (A)")
value = models.FloatField(null=True, blank=True) # преобразованное значение
status = models.CharField(max_length=20, blank=True, null=True) # No Rx, Error, NotAv и т.д.
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Метрика"
verbose_name_plural = "Метрики"
ordering = ["timestamp", "sensor"]
indexes = [
models.Index(fields=["timestamp"]),
models.Index(fields=["sensor"]),
]
def __str__(self):
return f"{self.timestamp} {self.sensor} = {self.value} {self.sensor.signal_format.unit if self.sensor.signal_format else ''}"
class Alert(models.Model):
"""Тревоги по метрикам"""
sensor = models.ForeignKey(Sensor, on_delete=models.CASCADE, related_name="alerts")
metric = models.ForeignKey(Metric, on_delete=models.CASCADE, related_name="alerts")
sensor_type = models.ForeignKey(SensorType, on_delete=models.CASCADE, related_name="alerts")
message = models.CharField(max_length=255)
severity = models.CharField(
max_length=20,
choices=[
("warning", "Warning"),
("critical", "Critical"),
],
default="warning"
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
resolved = models.BooleanField(default=False)
class Meta:
indexes = [
models.Index(fields=["created_at"]),
models.Index(fields=["sensor"]),
models.Index(fields=["sensor_type"]),
]
verbose_name = "Тревога"
verbose_name_plural = "Тревоги"
ordering = ["created_at", "sensor"]
def __str__(self):
return f"ALERT {self.sensor} @ {self.metric.timestamp}: {self.message}"