Files
dashboard-cpsp/backend/apps/operations/services/kpi_rollups.py
T

119 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Refresh daily KPI rows from ops sources and compute FCR / EEF."""
from __future__ import annotations
from datetime import date
from apps.farms.models import Cycle
from apps.operations.models import ChickenCounting, ChickenWeight, FeedSacks, KPI, ManualInput
from apps.operations.services.kpi_formulas import (
compute_average_harvest_day,
compute_average_harvest_weight,
compute_chicken_life,
compute_chicken_life_percentage,
compute_eef,
compute_fcr,
compute_stock_akhir,
)
from apps.operations.services.visibility import visible_through_date
def refresh_kpi_source_fields(cycle: Cycle, *, through: date | None = None) -> int:
"""
Upsert one KPI row per day that has cc + cw + fs + manual.
Returns the number of KPI rows written.
"""
cutoff = through if through is not None else visible_through_date()
start = cycle.start_date
if cycle.end_date and cycle.end_date < cutoff:
cutoff = cycle.end_date
manuals = {
row.date: row
for row in ManualInput.objects.filter(cycle=cycle, date__gte=start, date__lte=cutoff).order_by(
"date", "pk"
)
}
countings = {
row.date: row
for row in ChickenCounting.objects.filter(cycle=cycle, date__gte=start, date__lte=cutoff)
}
weights = {
row.date: row
for row in ChickenWeight.objects.filter(cycle=cycle, date__gte=start, date__lte=cutoff)
}
feeds = {
row.date: row
for row in FeedSacks.objects.filter(cycle=cycle, date__gte=start, date__lte=cutoff)
}
written = 0
harvest_age_sum = 0.0
for day in sorted(set(manuals) | set(countings) | set(weights) | set(feeds)):
if day < start or day > cutoff:
continue
manual = manuals.get(day)
cc = countings.get(day)
cw = weights.get(day)
fs = feeds.get(day)
if not all([manual, cc, cw, fs]):
# Still accumulate harvest age sum from manual-only days so later
# complete days keep a correct running Σ(age×harvest).
if manual is not None:
age = (day - start).days
harvest_age_sum += age * (manual.harvest_manual or 0)
continue
age = (day - start).days
harvest_today = manual.harvest_manual or 0
harvest_age_sum += age * harvest_today
mortality_total = manual.mortality_manual_total or 0
harvest_total = manual.harvest_manual_total or 0
harvest_weight_total_g = float(manual.harvest_weight_manual_total or 0.0)
feed_total = fs.feed_use_total or 0
iot_weight = float(cw.average_weight or 0.0)
chicken_life = compute_chicken_life(cycle.doc_in_count, mortality_total)
life_pct = compute_chicken_life_percentage(cycle.doc_in_count, chicken_life)
stock_akhir = compute_stock_akhir(chicken_life, harvest_total)
avg_bw = compute_average_harvest_weight(
harvest_weight_total_g, stock_akhir, iot_weight, harvest_total
)
avg_day = compute_average_harvest_day(harvest_age_sum, age, stock_akhir, harvest_total)
fcr = compute_fcr(feed_total, stock_akhir, iot_weight, harvest_weight_total_g)
eef = compute_eef(life_pct, avg_bw, avg_day, fcr)
KPI.objects.update_or_create(
cycle=cycle,
date=day,
defaults={
"age": age,
"mortality": manual.mortality_manual or 0,
"mortality_total": mortality_total,
"feed": fs.feed_use_today or 0,
"feed_total": feed_total,
"harvest": harvest_today,
"harvest_total": harvest_total,
"harvest_weight": float(manual.harvest_weight_manual or 0.0),
"harvest_weight_total": harvest_weight_total_g,
"chicken_life": chicken_life,
"chicken_life_percentage": life_pct,
"iot_weight": iot_weight,
"average_harvest_day": avg_day,
"average_harvest_weight": avg_bw,
"fcr": fcr,
"eef": eef,
"cc": cc,
"cw": cw,
"fs": fs,
"manual": manual,
},
)
written += 1
return written