From f629f6b2cc9458f768faa5e183d05ae12675cd59 Mon Sep 17 00:00:00 2001 From: Alex Lebedev Date: Thu, 2 Apr 2026 08:46:58 +0200 Subject: [PATCH] feat: Expose artefacts --- products/signals/backend/serializers.py | 43 ++++++++++++++++++++++++- products/signals/backend/views.py | 9 +++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/products/signals/backend/serializers.py b/products/signals/backend/serializers.py index 947a63f72c09..e031afa657ac 100644 --- a/products/signals/backend/serializers.py +++ b/products/signals/backend/serializers.py @@ -113,7 +113,13 @@ def validate(self, attrs: dict) -> dict: class SignalReportSerializer(serializers.ModelSerializer): artefact_count = serializers.IntegerField(read_only=True) priority = serializers.SerializerMethodField( - help_text="P0–P4 from the latest actionability judgment artefact (when present).", + help_text="P0–P4 from the latest priority judgment artefact (when present).", + ) + actionability = serializers.SerializerMethodField( + help_text="Actionability choice from the latest actionability judgment artefact (when present).", + ) + already_addressed = serializers.SerializerMethodField( + help_text="Whether the issue appears already fixed, from the actionability judgment artefact.", ) is_suggested_reviewer = serializers.BooleanField(read_only=True, default=False) @@ -131,10 +137,30 @@ class Meta: "updated_at", "artefact_count", "priority", + "actionability", + "already_addressed", "is_suggested_reviewer", ] read_only_fields = fields + def _get_actionability_artefact_data(self, obj: SignalReport) -> dict | None: + prefetched = getattr(obj, "prefetched_actionability_artefacts", None) + if prefetched is not None: + art = prefetched[0] if prefetched else None + else: + art = ( + obj.artefacts.filter(type=SignalReportArtefact.ArtefactType.ACTIONABILITY_JUDGMENT) + .order_by("-created_at") + .first() + ) + if art is None: + return None + try: + data = json.loads(art.content) + except (json.JSONDecodeError, TypeError, ValueError): + return None + return data if isinstance(data, dict) else None + def get_priority(self, obj: SignalReport) -> str | None: prefetched = getattr(obj, "prefetched_priority_artefacts", None) if prefetched is not None: @@ -156,6 +182,21 @@ def get_priority(self, obj: SignalReport) -> str | None: p = data.get("priority") return p if isinstance(p, str) else None + def get_actionability(self, obj: SignalReport) -> str | None: + data = self._get_actionability_artefact_data(obj) + if data is None: + return None + # Support both agentic ("actionability") and legacy ("choice") field names + value = data.get("actionability") or data.get("choice") + return value if isinstance(value, str) else None + + def get_already_addressed(self, obj: SignalReport) -> bool | None: + data = self._get_actionability_artefact_data(obj) + if data is None: + return None + value = data.get("already_addressed") + return value if isinstance(value, bool) else None + class SignalReportArtefactSerializer(serializers.ModelSerializer): content = serializers.SerializerMethodField() diff --git a/products/signals/backend/views.py b/products/signals/backend/views.py index 04f632c418e9..3ae69e4ef60e 100644 --- a/products/signals/backend/views.py +++ b/products/signals/backend/views.py @@ -299,7 +299,14 @@ def safely_get_queryset(self, queryset): type=SignalReportArtefact.ArtefactType.PRIORITY_JUDGMENT ).order_by("-created_at"), to_attr="prefetched_priority_artefacts", - ) + ), + Prefetch( + "artefacts", + queryset=SignalReportArtefact.objects.filter( + type=SignalReportArtefact.ArtefactType.ACTIONABILITY_JUDGMENT + ).order_by("-created_at"), + to_attr="prefetched_actionability_artefacts", + ), ) # Annotate is_suggested_reviewer by resolving the current user's GitHub login