Files
dashboard-cpsp-executive/backend/apps/jobs/management/commands/recompute_kpi_rollups.py
T
2026-09-08 15:21:23 +07:00

59 lines
2.4 KiB
Python

from django.core.management.base import BaseCommand
from django.db.models import Sum
from apps.farms.models import Cycle
from apps.operations.models import ChickenCounting, FeedSacks, KPI
from apps.operations.services.visibility import visible_through_date
class Command(BaseCommand):
help = "Recompute simple KPI rollups from ops tables for active cycles"
def handle(self, *args, **options):
cutoff = visible_through_date()
count = 0
for cycle in Cycle.objects.filter(status=Cycle.STATUS_ACTIVE):
def visible(model):
return model.objects.filter(cycle=cycle, date__lte=cutoff)
counting = visible(ChickenCounting).aggregate(
mortality=Sum("mortality_count"),
life=Sum("total_count"),
)
feed_sack = visible(FeedSacks).order_by("-date", "-pk").first()
latest_cc = visible(ChickenCounting).order_by("-date").first()
latest_cw = None
latest_fs = feed_sack
latest_mi = None
from apps.operations.models import ChickenWeight, ManualInput
latest_cw = visible(ChickenWeight).order_by("-date").first()
latest_mi = visible(ManualInput).order_by("-date").first()
if not all([latest_cc, latest_cw, latest_fs, latest_mi]):
self.stdout.write(f"cycle={cycle.pk} skipped (missing source records)")
continue
kpi, _ = KPI.objects.update_or_create(
cycle=cycle,
date=latest_cc.date,
defaults={
"cc": latest_cc,
"cw": latest_cw,
"fs": latest_fs,
"manual": latest_mi,
"mortality_total": counting["mortality"] or 0,
"chicken_life": counting["life"] or cycle.doc_in_count,
"feed_total": feed_sack.feed_use_total if feed_sack else 0,
"chicken_life_percentage": (
((counting["life"] or cycle.doc_in_count) / cycle.doc_in_count * 100.0)
if cycle.doc_in_count
else 0.0
),
},
)
count += 1
self.stdout.write(f"KPI {kpi.pk} for cycle {cycle.pk}")
self.stdout.write(self.style.SUCCESS(f"Recomputed {count} KPI row(s)"))