This commit is contained in:
2026-05-05 21:01:58 +03:30
parent 39efd537bf
commit 4e28bacad6
54 changed files with 2729 additions and 1115 deletions
+29
View File
@@ -0,0 +1,29 @@
EMPTY_ALERT_TRACKER = {
"totalAlerts": 0,
"radialBarValue": 0,
"alertStats": [],
"status": "empty",
"source": "db",
"warnings": ["No active farm alerts were found."],
}
EMPTY_ALERT_TIMELINE = {
"alerts": [],
"status": "empty",
"source": "db",
"warnings": ["No farm alert timeline entries were found."],
}
EMPTY_ANOMALY_CARD = {
"anomalies": [],
"status": "empty",
"source": "db",
"warnings": ["No persisted anomaly detections were found."],
}
EMPTY_RECOMMENDATIONS = {
"recommendations": [],
"status": "empty",
"source": "db",
"warnings": ["No persisted farm recommendations were found."],
}
+24 -17
View File
@@ -10,12 +10,7 @@ from farm_hub.models import FarmHub
from notifications.models import FarmNotification
from notifications.services import create_notification_for_farm_uuid, get_recent_notifications_for_farm
from .mock_data import (
ANOMALY_DETECTION_CARD,
ARM_ALERTS_TRACKER,
FARM_ALERTS_TIMELINE,
RECOMMENDATIONS_LIST,
)
from .defaults import EMPTY_ALERT_TIMELINE, EMPTY_ALERT_TRACKER, EMPTY_ANOMALY_CARD, EMPTY_RECOMMENDATIONS
from .models import AnomalyDetection, FarmAlert, FarmAlertTrackerSnapshot, Recommendation
@@ -383,11 +378,11 @@ def sync_all_farm_alert_trackers():
return {"processed": len(results), "results": results}
def get_alert_tracker_data(farm=None):
if farm is None:
return deepcopy(ARM_ALERTS_TRACKER)
return deepcopy(EMPTY_ALERT_TRACKER)
alerts = list(FarmAlert.objects.filter(farm=farm, is_active=True)[:20])
if not alerts:
return deepcopy(ARM_ALERTS_TRACKER)
return deepcopy(EMPTY_ALERT_TRACKER)
counts = Counter(alert.title for alert in alerts)
alert_stats = []
@@ -406,16 +401,19 @@ def get_alert_tracker_data(farm=None):
"totalAlerts": len(alerts),
"radialBarValue": min(len(alerts) * 10, 100),
"alertStats": alert_stats,
"status": "success",
"source": "db",
"warnings": [],
}
def get_alert_timeline_data(farm=None):
if farm is None:
return deepcopy(FARM_ALERTS_TIMELINE)
return deepcopy(EMPTY_ALERT_TIMELINE)
alerts = list(FarmAlert.objects.filter(farm=farm)[:10])
if not alerts:
return deepcopy(FARM_ALERTS_TIMELINE)
return deepcopy(EMPTY_ALERT_TIMELINE)
return {
"alerts": [
@@ -426,17 +424,20 @@ def get_alert_timeline_data(farm=None):
"color": alert.color,
}
for alert in alerts
]
],
"status": "success",
"source": "db",
"warnings": [],
}
def get_anomaly_detection_data(farm=None):
if farm is None:
return deepcopy(ANOMALY_DETECTION_CARD)
return deepcopy(EMPTY_ANOMALY_CARD)
anomalies = list(AnomalyDetection.objects.filter(farm=farm)[:10])
if not anomalies:
return deepcopy(ANOMALY_DETECTION_CARD)
return deepcopy(EMPTY_ANOMALY_CARD)
return {
"anomalies": [
@@ -448,17 +449,20 @@ def get_anomaly_detection_data(farm=None):
"severity": anomaly.severity,
}
for anomaly in anomalies
]
],
"status": "success",
"source": "db",
"warnings": [],
}
def get_recommendations_list_data(farm=None):
if farm is None:
return deepcopy(RECOMMENDATIONS_LIST)
return deepcopy(EMPTY_RECOMMENDATIONS)
recommendations = list(Recommendation.objects.filter(farm=farm)[:10])
if not recommendations:
return deepcopy(RECOMMENDATIONS_LIST)
return deepcopy(EMPTY_RECOMMENDATIONS)
return {
"recommendations": [
@@ -469,5 +473,8 @@ def get_recommendations_list_data(farm=None):
"avatarColor": recommendation.avatar_color or "info",
}
for recommendation in recommendations
]
],
"status": "success",
"source": "db",
"warnings": [],
}
+3
View File
@@ -89,6 +89,9 @@ class FarmAlertsTrackerViewTests(TestCase):
self.assertEqual(response.data["data"]["status_level"], "warning")
self.assertEqual(len(response.data["data"]["notifications"]), 1)
self.assertEqual(response.data["data"]["notifications"][0]["endpoint"], "tracker")
self.assertEqual(response.data["meta"]["flow_type"], "cached_snapshot")
self.assertTrue(response.data["meta"]["cached"])
self.assertEqual(response.data["meta"]["ownership"], "backend")
def test_tracker_limits_cached_notifications_to_ten(self):
for index in range(12):
+20 -1
View File
@@ -6,6 +6,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from config.integration_contract import build_integration_meta
from config.swagger import code_response
from farm_hub.models import FarmHub
@@ -61,4 +62,22 @@ class AlertTrackerView(FarmAlertsBaseView):
response_data,
)
serializer = AlertTrackerAIResponseSerializer(instance=response_data)
return Response({"code": 200, "msg": "success", "data": serializer.data}, status=status.HTTP_200_OK)
snapshot = getattr(farm, "alert_tracker_snapshot", None)
return Response(
{
"code": 200,
"msg": "success",
"data": serializer.data,
"meta": build_integration_meta(
flow_type="cached_snapshot",
source_type="cached_snapshot",
source_service="backend_farm_alerts_snapshot",
ownership="backend",
live=False,
cached=True,
snapshot_at=getattr(snapshot, "updated_at", None),
notes=["Returns persisted tracker snapshot, not live AI inference."],
),
},
status=status.HTTP_200_OK,
)