update applying current ai insight from dashboard-cpsp
This commit is contained in:
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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user