update applying current ai insight from dashboard-cpsp

This commit is contained in:
Alberto-Audrix committed 2026-10-05 14:25:22 +07:00
1 parent 31272035e9
commit b87f20e3b3
27 files changed
+5300 -343

No files matched your search

@@ -0,0 +1,77 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("farms", "0020_kandang_flock_source_ids"),
("operations", "0011_aiinsight_unique_cycle_date_section_session"),
]
operations = [
migrations.RemoveConstraint(
model_name="aiinsight",
name="uniq_ai_insight_cycle_date_section_session",
),
migrations.RemoveField(
model_name="aiinsight",
name="section",
),
migrations.RemoveField(
model_name="aiinsight",
name="session",
),
migrations.AddField(
model_name="aiinsight",
name="kandang",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="ai_insights",
to="farms.kandang",
),
),
migrations.AddField(
model_name="aiinsight",
name="topic",
field=models.CharField(blank=True, db_index=True, default="", max_length=64),
),
migrations.AddField(
model_name="aiinsight",
name="report_type",
field=models.CharField(blank=True, db_index=True, default="page", max_length=32),
),
migrations.AddField(
model_name="aiinsight",
name="report_period",
field=models.CharField(blank=True, db_index=True, default="current", max_length=64),
),
migrations.AddField(
model_name="aiinsight",
name="summary",
field=models.TextField(blank=True, default=""),
),
migrations.AddField(
model_name="aiinsight",
name="citations",
field=models.TextField(blank=True, default="[]"),
),
migrations.AlterField(
model_name="aiinsight",
name="alert",
field=models.CharField(blank=True, default="", max_length=100),
),
migrations.AlterModelOptions(
name="aiinsight",
options={"ordering": ["-date", "-created_at"]},
),
migrations.AddConstraint(
model_name="aiinsight",
constraint=models.UniqueConstraint(
fields=("cycle", "kandang", "topic", "report_type", "report_period"),
name="uniq_ai_insight_cache_scope",
),
),
]
+17 -7
View File
@@ -29,21 +29,31 @@ class IotPanel(models.Model):
class AIInsight(models.Model):
date = models.DateField()
insight_text = models.TextField()
alert = models.CharField(max_length=100)
section = models.CharField(max_length=100)
session = models.CharField(max_length=100)
alert = models.CharField(max_length=100, blank=True, default="")
cycle = models.ForeignKey(Cycle, on_delete=models.CASCADE, related_name="ai_insights")
kandang = models.ForeignKey(
"farms.Kandang",
on_delete=models.CASCADE,
related_name="ai_insights",
null=True,
blank=True,
)
topic = models.CharField(max_length=64, blank=True, default="", db_index=True)
report_type = models.CharField(max_length=32, blank=True, default="page", db_index=True)
report_period = models.CharField(max_length=64, blank=True, default="current", db_index=True)
summary = models.TextField(blank=True, default="")
citations = models.TextField(blank=True, default="[]")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "ai_insight"
ordering = ["-date"]
ordering = ["-date", "-created_at"]
constraints = [
models.UniqueConstraint(
fields=["cycle", "date", "section", "session"],
name="uniq_ai_insight_cycle_date_section_session",
),
fields=["cycle", "kandang", "topic", "report_type", "report_period"],
name="uniq_ai_insight_cache_scope",
)
]
+15
View File
@@ -136,3 +136,18 @@ class AIInsightSerializer(PkAsIdMixin, CycleContextMixin, serializers.ModelSeria
model = AIInsight
fields = "__all__"
read_only_fields = ["id", "created_at", "updated_at"]
def to_representation(self, instance):
import json
data = super().to_representation(instance)
raw_citations = getattr(instance, "citations", "")
if isinstance(raw_citations, list):
data["citations"] = raw_citations
elif isinstance(raw_citations, str) and raw_citations.strip():
try:
data["citations"] = json.loads(raw_citations)
except Exception:
data["citations"] = []
else:
data["citations"] = []
return data
+110 -1
View File
@@ -236,9 +236,118 @@ class IotPanelViewSet(viewsets.ModelViewSet):
class AIInsightViewSet(CycleScopedViewSet):
queryset = AIInsight.objects.select_related("cycle").all()
queryset = AIInsight.objects.select_related("cycle", "kandang").all()
serializer_class = AIInsightSerializer
def get_queryset(self):
qs = super().get_queryset()
params = self.request.query_params
if params.get("kandang_id"):
qs = qs.filter(kandang_id=params["kandang_id"])
if params.get("topic"):
qs = qs.filter(topic=params["topic"])
if params.get("report_type"):
qs = qs.filter(report_type=params["report_type"])
if params.get("report_period"):
qs = qs.filter(report_period=params["report_period"])
return qs
@action(detail=False, methods=["get"], url_path="cached")
def cached(self, request):
import json
params = request.query_params
cycle_id = params.get("cycle_id")
kandang_id = params.get("kandang_id")
topic = params.get("topic")
if not cycle_id or not kandang_id or not topic:
return Response(
{"detail": "cycle_id, kandang_id, and topic are required."},
status=status.HTTP_400_BAD_REQUEST,
)
report_type = params.get("report_type") or "page"
report_period = params.get("report_period") or "current"
row = (
AIInsight.objects.filter(
cycle_id=cycle_id,
kandang_id=kandang_id,
topic=topic,
report_type=report_type,
report_period=report_period,
)
.order_by("-updated_at")
.first()
)
if row is None:
return Response({"detail": "No cached insight."}, status=status.HTTP_404_NOT_FOUND)
raw_cites = row.citations
if isinstance(raw_cites, list):
cites = raw_cites
elif isinstance(raw_cites, str) and raw_cites.strip():
try:
cites = json.loads(raw_cites)
except Exception:
cites = []
else:
cites = []
return Response({
"success": True,
"insight_text": row.insight_text,
"summary": row.summary,
"alert": row.alert,
"citations": cites,
"topic": row.topic,
"report_type": row.report_type,
"report_period": row.report_period,
})
@action(detail=False, methods=["post"], url_path="generate")
def generate(self, request):
params = request.data or {}
cycle_id = params.get("cycle_id")
kandang_id = params.get("kandang_id")
topic = params.get("topic")
if not cycle_id or not kandang_id or not topic:
return Response(
{"detail": "cycle_id, kandang_id, and topic are required."},
status=status.HTTP_400_BAD_REQUEST,
)
report_type = params.get("report_type") or "page"
report_period = params.get("report_period") or "current"
row = (
AIInsight.objects.filter(
cycle_id=cycle_id,
kandang_id=kandang_id,
topic=topic,
report_type=report_type,
report_period=report_period,
)
.order_by("-updated_at")
.first()
)
if row is not None:
import json
raw_cites = row.citations
try:
cites = json.loads(raw_cites) if isinstance(raw_cites, str) else (raw_cites or [])
except Exception:
cites = []
return Response({
"success": True,
"insight_text": row.insight_text,
"summary": row.summary,
"alert": row.alert,
"citations": cites,
"topic": row.topic,
"report_type": row.report_type,
"report_period": row.report_period,
})
return Response(
{"detail": "Insight belum tersedia pada mirror HQ. Pastikan insight telah di-generate pada site dashboard dan disinkronkan."},
status=status.HTTP_404_NOT_FOUND,
)
class HealthView(APIView):
permission_classes = [AllowAny]
+25 -10
View File
@@ -421,21 +421,36 @@ def _upsert_kpis(cycle: Cycle, rows: list[dict[str, Any]]) -> int:
def _upsert_ai_insights(cycle: Cycle, rows: list[dict[str, Any]]) -> int:
import json
count = 0
for raw in rows:
row_date = _as_date(raw.get("date"))
section = str(raw.get("section") or "")[:100]
session = str(raw.get("session") or "")[:100]
if row_date is None:
continue
row_date = _as_date(raw.get("date")) or cycle.start_date
topic = str(raw.get("topic") or "")[:64]
report_type = str(raw.get("report_type") or "page")[:32]
report_period = str(raw.get("report_period") or "current")[:64]
insight_text = str(raw.get("insight_text") or "")
summary = str(raw.get("summary") or "")
alert = str(raw.get("alert") or "")[:100]
raw_citations = raw.get("citations")
if isinstance(raw_citations, (list, dict)):
citations_str = json.dumps(raw_citations, ensure_ascii=False)
elif isinstance(raw_citations, str):
citations_str = raw_citations
else:
citations_str = "[]"
AIInsight.objects.update_or_create(
cycle=cycle,
date=row_date,
section=section,
session=session,
kandang=cycle.kandang,
topic=topic,
report_type=report_type,
report_period=report_period,
defaults={
"insight_text": str(raw.get("insight_text") or ""),
"alert": str(raw.get("alert") or "")[:100],
"date": row_date,
"insight_text": insight_text,
"summary": summary,
"alert": alert,
"citations": citations_str,
},
)
count += 1