import type { DashboardReportPayload } from '../types/dashboardReport.ts'; import { countingTotals, feedInTotals, feedOutTotals, feedUseTotals } from './reportTotals.ts'; import { formatNumber, formatRatio } from './format.ts'; export type ReportSummaryMetricKey = | 'umur' | 'docWeight' | 'populasi' | 'mortalitas' | 'panen' | 'beratPanen' | 'karungMasuk' | 'karungDituang' | 'beratPakan' | 'karungKeluar' | 'beratAvg' | 'uniformity' | 'fcr' | 'eef'; export interface ReportSummaryMetric { key: ReportSummaryMetricKey; label: string; value: number | null; unit: string; } export interface ReportSummary { metrics: ReportSummaryMetric[]; } const isPresent = (value: number | null | undefined): value is number => typeof value === 'number' && Number.isFinite(value); /** kg per karung — same factor as Dashboard feed weight panel */ const FEED_SACK_KG = 50; export function latestValidRatio( rows: Array<{ iotFcr?: number | null; iotEef?: number | null }>, key: 'iotFcr' | 'iotEef' ): number | null { for (let i = rows.length - 1; i >= 0; i -= 1) { const value = rows[i]?.[key]; if (typeof value === 'number' && Number.isFinite(value)) return value; } return null; } export function buildReportSummary(payload: DashboardReportPayload): ReportSummary { const lastCounting = payload.counting[payload.counting.length - 1] ?? null; const lastWeight = payload.weight[payload.weight.length - 1] ?? null; const counting = countingTotals(payload.counting); const feedIn = feedInTotals(payload.feedIn); const feedUse = feedUseTotals(payload.feedUse); const feedOut = feedOutTotals(payload.feedOut); const beratAvg = lastWeight ? isPresent(lastWeight.avgIot) ? lastWeight.avgIot : isPresent(lastWeight.avgManual) ? lastWeight.avgManual : null : null; const metrics: ReportSummaryMetric[] = [ { key: 'umur', label: 'Umur', value: payload.upToDay, unit: 'hari' }, { key: 'docWeight', label: 'Berat DOC', value: isPresent(payload.docWeight) ? payload.docWeight : null, unit: 'gram', }, { key: 'populasi', label: 'Populasi', value: isPresent(lastCounting?.countAfter) ? lastCounting!.countAfter : null, unit: 'ekor', }, { key: 'mortalitas', label: 'Mortalitas', value: counting?.mortality ?? null, unit: 'ekor', }, { key: 'panen', label: 'Panen', value: counting?.harvest ?? null, unit: 'ekor', }, { key: 'beratPanen', label: 'Berat Panen', // counting harvestWeight is stored in grams; surface kg for the summary card value: isPresent(counting?.harvestWeight) ? counting!.harvestWeight / 1000 : null, unit: 'kg', }, { key: 'karungMasuk', label: 'Karung Masuk', value: feedIn?.manualIn ?? null, unit: 'karung', }, { key: 'karungDituang', label: 'Karung Dituang', value: feedUse?.manualUse ?? null, unit: 'karung', }, { key: 'beratPakan', label: 'Berat Pakan', value: isPresent(feedUse?.manualUse) ? feedUse!.manualUse * FEED_SACK_KG : null, unit: 'kg', }, { key: 'karungKeluar', label: 'Karung Keluar', value: feedOut?.manualOut ?? null, unit: 'karung', }, { key: 'beratAvg', label: 'Berat AVG', value: beratAvg, unit: 'gram' }, { key: 'uniformity', label: 'Uniformity', value: isPresent(lastWeight?.uniformity) ? lastWeight!.uniformity : null, unit: '%', }, { key: 'fcr', label: 'FCR', value: latestValidRatio(payload.fcr, 'iotFcr'), unit: '', }, { key: 'eef', label: 'EEF', value: latestValidRatio(payload.eef, 'iotEef'), unit: '', }, ]; return { metrics }; } export function formatSummaryValue(metric: ReportSummaryMetric): string { if (!isPresent(metric.value)) return '—'; if (metric.key === 'fcr' || metric.key === 'eef') { return formatRatio(metric.value, 3); } if ( metric.key === 'uniformity' || metric.key === 'beratAvg' || metric.key === 'beratPanen' || metric.key === 'beratPakan' || metric.key === 'docWeight' ) { return formatNumber(metric.value, 1, 0, true); } return formatNumber(metric.value, 0, 0, true); }