Files
dashboard-cpsp/utils/__tests__/reportSummary.test.ts
T

207 lines
5.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { buildReportSummary, latestValidRatio } from '../reportSummary.ts';
import type { ReportSummaryMetric, ReportSummaryMetricKey } from '../reportSummary.ts';
import type { DashboardReportPayload } from '../../types/dashboardReport.ts';
function emptyPayload(overrides: Partial<DashboardReportPayload> = {}): DashboardReportPayload {
return {
cycleId: '1',
cycleStart: '2026-05-01',
cycleEnd: '2026-06-04',
kandangId: 9,
kandangName: 'Kandang 1',
area: 'Area A',
upToDay: 2,
generatedAt: '2026-05-03T00:00:00.000Z',
reportType: 'daily',
counting: [],
weight: [],
feedIn: [],
feedUse: [],
feedOut: [],
fcr: [],
eef: [],
...overrides,
};
}
describe('latestValidRatio', () => {
it('returns the last finite value walking backward', () => {
expect(
latestValidRatio(
[
{ iotFcr: 1.1 },
{ iotFcr: null },
{ iotFcr: 1.4 },
{ iotFcr: null },
],
'iotFcr'
)
).toBe(1.4);
});
it('returns null when no finite values exist', () => {
expect(latestValidRatio([{ iotEef: null }, { iotEef: null }], 'iotEef')).toBeNull();
});
});
describe('buildReportSummary', () => {
it('returns 13 metrics in the agreed order with nulls when sections are empty', () => {
const summary = buildReportSummary(emptyPayload({ upToDay: 5 }));
expect(summary.metrics.map((m) => m.key)).toEqual([
'umur',
'populasi',
'mortalitas',
'panen',
'beratPanen',
'karungMasuk',
'karungDituang',
'beratPakan',
'karungKeluar',
'beratAvg',
'uniformity',
'fcr',
'eef',
]);
expect(summary.metrics[0]).toMatchObject({ label: 'Umur', value: 5, unit: 'hari' });
expect(summary.metrics.slice(1).every((m) => m.value === null)).toBe(true);
});
it('derives totals, latest populasi/weight, and FCR/EEF from payload rows', () => {
const summary = buildReportSummary(
emptyPayload({
upToDay: 2,
counting: [
{
date: '2026-05-01',
day: 0,
countBefore: 20000,
mortality: 10,
harvest: 0,
harvestWeight: 0,
countAfter: 19990,
iotCount: null,
accuracy: null,
},
{
date: '2026-05-03',
day: 2,
countBefore: 19980,
mortality: 5,
harvest: 100,
harvestWeight: 150000,
countAfter: 19875,
iotCount: 19870,
accuracy: 99.9,
},
],
weight: [
{
date: '2026-05-01',
day: 0,
avgManual: 45,
avgIot: 46,
uniformity: 80,
difference: 1,
accuracy: 97,
},
{
date: '2026-05-03',
day: 2,
avgManual: 900,
avgIot: 910,
uniformity: 88.5,
difference: 10,
accuracy: 98,
},
],
feedIn: [
{ date: '2026-05-01', manualIn: 20, iotIn: 21, accuracy: 95 },
{ date: '2026-05-03', manualIn: 10, iotIn: 10, accuracy: 100 },
],
feedUse: [
{ date: '2026-05-01', day: 0, manualUse: 8, iotUse: 8, accuracy: 100 },
{ date: '2026-05-03', day: 2, manualUse: 12, iotUse: 11, accuracy: 91 },
],
feedOut: [
{ date: '2026-05-01', manualOut: 1, iotOut: 1, accuracy: 100 },
{ date: '2026-05-03', manualOut: 2, iotOut: 2, accuracy: 100 },
],
fcr: [
{
day: 0,
iotFcr: 0.5,
feedTotal: 1,
iotWeight: 46,
chickenLife: 19990,
harvestWeightTotal: 0,
},
{
day: 2,
iotFcr: 1.25,
feedTotal: 20,
iotWeight: 910,
chickenLife: 19875,
harvestWeightTotal: 150,
},
],
eef: [
{
day: 0,
iotEef: 100,
chickenLifePercentage: 99.9,
iotAverageHarvestBw: null,
iotAverageDayWeight: 46,
iotFcr: 0.5,
},
{
day: 2,
iotEef: 320,
chickenLifePercentage: 99.4,
iotAverageHarvestBw: 1500,
iotAverageDayWeight: 910,
iotFcr: 1.25,
},
],
})
);
const byKey = Object.fromEntries(summary.metrics.map((m) => [m.key, m])) as Record<
ReportSummaryMetricKey,
ReportSummaryMetric
>;
expect(byKey.umur.value).toBe(2);
expect(byKey.populasi.value).toBe(19875);
expect(byKey.mortalitas.value).toBe(15);
expect(byKey.panen.value).toBe(100);
expect(byKey.beratPanen.value).toBe(150);
expect(byKey.karungMasuk.value).toBe(30);
expect(byKey.karungDituang.value).toBe(20);
expect(byKey.beratPakan.value).toBe(1000);
expect(byKey.karungKeluar.value).toBe(3);
expect(byKey.beratAvg.value).toBe(910);
expect(byKey.uniformity.value).toBe(88.5);
expect(byKey.fcr.value).toBe(1.25);
expect(byKey.eef.value).toBe(320);
});
it('falls back to avgManual when avgIot is null on the latest weight row', () => {
const summary = buildReportSummary(
emptyPayload({
weight: [
{
date: '2026-05-03',
day: 2,
avgManual: 905,
avgIot: null,
uniformity: 90,
difference: null,
accuracy: null,
},
],
})
);
expect(summary.metrics.find((m) => m.key === 'beratAvg')?.value).toBe(905);
});
});