kpi configuration (still prototype some data still use manual need to update later)

This commit is contained in:
Alberto-Audrix committed 2026-09-09 16:50:07 +07:00
1 parent e4dbfd0252
commit ed84d989b7
12 files changed
+547 -89

No files matched your search

@@ -0,0 +1,118 @@
"""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