Coverage for backend/django/diagnostics/views.py: 86%
53 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-23 21:51 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-23 21:51 +0000
1"""
2Diagnostics API views.
4These endpoints exist to support the diagnostics UI:
5- Run light-weight "what if?" validations used by the properties panel
7The goal is to keep views fairly thin: parse/validate the request, fetch the
8minimal DB state needed, then delegate to the diagnostics rules engine.
9"""
11from __future__ import annotations
13from django.db.models import Prefetch
14from diagnostics.methods.get_solver_rule_findings import get_flowsheet_rule_findings, get_object_rule_findings
15from drf_spectacular.types import OpenApiTypes
16from drf_spectacular.utils import OpenApiParameter, extend_schema
17from rest_framework import status
18from rest_framework.decorators import api_view
19from rest_framework.response import Response
21from core.validation import api_view_validate
22from diagnostics.rules.engine import RuleFinding, evaluate_property_rules
23from diagnostics.serializers import (
24 FlowsheetRuleFindingsSerializer,
25 PropertyRuleFindingsSerializer,
26)
27from core.auxiliary.models.PropertyInfo import PropertyInfo
28from core.auxiliary.models.PropertyValue import PropertyValue
29from flowsheetInternals.unitops.models.SimulationObject import SimulationObject
31def _get_simulation_object_for_rules(object_id: int) -> SimulationObject:
32 """
33 Fetch a SimulationObject with properties eagerly loaded for rule evaluation.
35 I prefetch properties/values here to avoid N+1 queries when we walk through
36 all properties during rule evaluation.
37 """
38 qs = SimulationObject.objects.filter(id=object_id).select_related("properties")
39 values_qs = PropertyValue.objects.select_related(
40 "controlManipulated",
41 "controlSetPoint",
42 )
44 return qs.prefetch_related(
45 Prefetch(
46 "properties__ContainedProperties",
47 queryset=PropertyInfo.objects.select_related("recycleConnection").prefetch_related(
48 Prefetch("values", queryset=values_qs)
49 ),
50 )
51 ).get()
54def _get_simulation_objects_for_rules(flowsheet_id: int):
55 """
56 Fetch all SimulationObjects in a flowsheet with properties eagerly loaded.
57 """
58 values_qs = PropertyValue.objects.select_related(
59 "controlManipulated",
60 "controlSetPoint",
61 )
62 return (
63 SimulationObject.objects.filter(flowsheet_id=flowsheet_id)
64 .select_related("properties")
65 .prefetch_related(
66 Prefetch(
67 "properties__ContainedProperties",
68 queryset=PropertyInfo.objects.select_related("recycleConnection").prefetch_related(
69 Prefetch("values", queryset=values_qs)
70 ),
71 )
72 )
73 )
76@extend_schema(
77 operation_id="diagnostics_evaluate_simulation_object_property_rules",
78 parameters=[
79 OpenApiParameter(name="simulation_object_id", type=OpenApiTypes.INT, location=OpenApiParameter.PATH),
80 OpenApiParameter(
81 name="flowsheet",
82 type=OpenApiTypes.INT,
83 location=OpenApiParameter.QUERY,
84 required=True,
85 ),
86 ],
87 responses={200: PropertyRuleFindingsSerializer},
88)
89@api_view_validate
90@api_view(["GET"])
91def evaluate_object_property_rules(request, simulation_object_id: int) -> Response:
92 """
93 Evaluate deterministic diagnostics rules against a unit op's current properties.
94 Intended for inline properties-panel warnings/suggestions/errors.
95 """
96 try:
97 obj = _get_simulation_object_for_rules(simulation_object_id)
98 except SimulationObject.DoesNotExist:
99 return Response(status=status.HTTP_404_NOT_FOUND)
101 findings = evaluate_property_rules(obj)
102 findings.extend(get_object_rule_findings(simulation_object_id))
103 response_serializer = PropertyRuleFindingsSerializer(
104 {"object_id": obj.id, "findings": [f.to_dict() for f in findings]}
105 )
106 return Response(response_serializer.data)
109@extend_schema(
110 operation_id="diagnostics_evaluate_flowsheet_property_rules",
111 parameters=[
112 OpenApiParameter(name="flowsheet_id", type=OpenApiTypes.INT, location=OpenApiParameter.PATH),
113 OpenApiParameter(
114 name="flowsheet",
115 type=OpenApiTypes.INT,
116 location=OpenApiParameter.QUERY,
117 required=True,
118 ),
119 ],
120 responses={200: FlowsheetRuleFindingsSerializer},
121)
122@api_view_validate
123@api_view(["GET"])
124def evaluate_flowsheet_property_rules(request, flowsheet_id: int) -> Response:
125 """
126 Evaluate deterministic diagnostics rules across all flowsheet objects.
127 """
128 try:
129 query_flowsheet_id = int(request.GET.get("flowsheet"))
130 except (TypeError, ValueError):
131 return Response({"detail": "Invalid flowsheet id"}, status=status.HTTP_400_BAD_REQUEST)
133 if query_flowsheet_id != flowsheet_id: 133 ↛ 134line 133 didn't jump to line 134 because the condition on line 133 was never true
134 return Response(
135 {"detail": "Flowsheet ID mismatch between path and query parameter"},
136 status=status.HTTP_400_BAD_REQUEST,
137 )
139 objects = _get_simulation_objects_for_rules(flowsheet_id)
140 findings: list[RuleFinding] = []
141 for obj in objects:
142 try:
143 findings.extend(evaluate_property_rules(obj))
144 except Exception:
145 # Keep the endpoint resilient: one malformed object path should not
146 # prevent diagnostics from being returned for the rest of the flowsheet.
147 continue
149 findings.extend(get_flowsheet_rule_findings(flowsheet_id))
151 response_serializer = FlowsheetRuleFindingsSerializer(
152 {"flowsheet_id": flowsheet_id, "findings": [f.to_dict() for f in findings]}
153 )
154 return Response(response_serializer.data)