146 lines
4.5 KiB
TypeScript
146 lines
4.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import {
|
||
buildDashboardInsightMetrics,
|
||
scopeDashboardInsightMetrics,
|
||
} from '../insightContextBuilders.ts';
|
||
import { getTargetWeightForAge } from '../weight.ts';
|
||
|
||
describe('buildDashboardInsightMetrics', () => {
|
||
it('builds latest scalars and day-indexed weight series with targets', () => {
|
||
const metrics = buildDashboardInsightMetrics({
|
||
kpis: [
|
||
{
|
||
age: 10,
|
||
fcr: 1.2,
|
||
eef: 180,
|
||
chicken_life: 9800,
|
||
chicken_life_percentage: 98,
|
||
iot_weight: 0,
|
||
mortality_total: 200,
|
||
harvest_total: 0,
|
||
harvest_weight_total: 0,
|
||
feed_total: 120,
|
||
},
|
||
{
|
||
age: 28,
|
||
fcr: 1.55,
|
||
eef: 260,
|
||
chicken_life: 9500,
|
||
chicken_life_percentage: 95,
|
||
iot_weight: 1400,
|
||
mortality_total: 500,
|
||
harvest_total: 100,
|
||
harvest_weight_total: 150_500, // grams
|
||
feed_total: 400,
|
||
},
|
||
],
|
||
weights: [
|
||
{ age: 10, average_weight: 280, doc_weight: 40, uniformity: 82.5 },
|
||
{ age: 28, average_weight: 1350, doc_weight: 40, uniformity: 78.1 },
|
||
],
|
||
currentDay: 30,
|
||
docInCount: 10000,
|
||
});
|
||
|
||
expect(metrics.hari_ke).toBe(28);
|
||
expect(metrics.fcr).toBe(1.55);
|
||
expect(metrics.eef).toBe(260);
|
||
expect(metrics.ayam_hidup_ekor).toBe(9400); // 9500 life − 100 harvest
|
||
expect(metrics.mortalitas_kumulatif_persen).toBe(5);
|
||
expect(metrics.bobot_avg_gram).toBe(1400);
|
||
expect(metrics.doc_in_ekor).toBe(10000);
|
||
expect(metrics.kematian_kumulatif_ekor).toBe(500);
|
||
expect(metrics.panen_kumulatif_ekor).toBe(100);
|
||
expect(metrics.tonase_panen_kg).toBe(150.5);
|
||
expect(metrics.uniformity_persen).toBe(78.1);
|
||
expect(metrics.pakan_kumulatif_karung).toBe(400);
|
||
expect(metrics.weightSeries).toHaveLength(2);
|
||
expect(metrics.weightSeries[0]).toEqual({
|
||
hari: 10,
|
||
actual: 280,
|
||
target: getTargetWeightForAge(10, 40),
|
||
});
|
||
expect(metrics.kpiSeries.map((row) => row.hari)).toEqual([10, 28]);
|
||
});
|
||
|
||
it('falls back to weight average when iot_weight is missing', () => {
|
||
const metrics = buildDashboardInsightMetrics({
|
||
kpis: [
|
||
{
|
||
age: 7,
|
||
fcr: 1.1,
|
||
eef: 100,
|
||
chicken_life: 9900,
|
||
chicken_life_percentage: 99,
|
||
iot_weight: 0,
|
||
},
|
||
],
|
||
weights: [{ age: 7, average_weight: 190, doc_weight: 40 }],
|
||
pakanKumulatifFallback: 55,
|
||
});
|
||
|
||
expect(metrics.bobot_avg_gram).toBe(190);
|
||
expect(metrics.pakan_kumulatif_karung).toBe(55);
|
||
});
|
||
});
|
||
|
||
describe('scopeDashboardInsightMetrics', () => {
|
||
const base = buildDashboardInsightMetrics({
|
||
kpis: [
|
||
{
|
||
age: 7,
|
||
fcr: 1.1,
|
||
eef: 90,
|
||
chicken_life: 9900,
|
||
chicken_life_percentage: 99,
|
||
iot_weight: 200,
|
||
mortality_total: 100,
|
||
harvest_total: 0,
|
||
harvest_weight_total: 0,
|
||
feed_total: 40,
|
||
},
|
||
{
|
||
age: 14,
|
||
fcr: 1.3,
|
||
eef: 150,
|
||
chicken_life: 9800,
|
||
chicken_life_percentage: 98,
|
||
iot_weight: 450,
|
||
mortality_total: 200,
|
||
harvest_total: 50,
|
||
harvest_weight_total: 80_000, // grams
|
||
feed_total: 90,
|
||
},
|
||
],
|
||
weights: [
|
||
{ age: 7, average_weight: 200, doc_weight: 40, uniformity: 85 },
|
||
{ age: 14, average_weight: 450, doc_weight: 40, uniformity: 80 },
|
||
{ age: 21, average_weight: 800, doc_weight: 40, uniformity: 75 },
|
||
],
|
||
docInCount: 10000,
|
||
});
|
||
|
||
it('filters weight series and uses exact-day KPI scalars', () => {
|
||
const scoped = scopeDashboardInsightMetrics(base, 7);
|
||
expect(scoped.hari_ke).toBe(7);
|
||
expect(scoped.fcr).toBe(1.1);
|
||
expect(scoped.bobot_avg_gram).toBe(200);
|
||
expect(scoped.doc_in_ekor).toBe(10000);
|
||
expect(scoped.kematian_kumulatif_ekor).toBe(100);
|
||
expect(scoped.pakan_kumulatif_karung).toBe(40);
|
||
expect(scoped.uniformity_persen).toBe(85);
|
||
expect(scoped.weightSeries.map((row) => row.hari)).toEqual([7]);
|
||
});
|
||
|
||
it('does not invent scalars for a day without KPI snapshot', () => {
|
||
const scoped = scopeDashboardInsightMetrics(base, 21);
|
||
expect(scoped.hari_ke).toBe(21);
|
||
expect(scoped.fcr).toBeNull();
|
||
expect(scoped.eef).toBeNull();
|
||
expect(scoped.bobot_avg_gram).toBe(800); // from weight series at day 21
|
||
expect(scoped.kematian_kumulatif_ekor).toBeNull();
|
||
expect(scoped.doc_in_ekor).toBe(10000); // cycle constant still shown
|
||
expect(scoped.weightSeries.map((row) => row.hari)).toEqual([7, 14, 21]);
|
||
});
|
||
});
|