60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
/**
|
||
* IoT-vs-manual accuracy for the Dashboard daily report.
|
||
*
|
||
* Smaller/larger × 100%, matching Hitung Ayam (`DataManagementPage`) and
|
||
* Hitung Karung (`FeedDailySummaryTable`). Null means no accuracy / N/A.
|
||
*/
|
||
|
||
const round1 = (value: number): number => Math.round(value * 10) / 10;
|
||
|
||
const minMaxPercent = (a: number, b: number): number =>
|
||
round1((Math.min(a, b) / Math.max(a, b)) * 100);
|
||
|
||
const isPresentNumber = (value: number | null | undefined): value is number =>
|
||
typeof value === 'number' && Number.isFinite(value);
|
||
|
||
/**
|
||
* Counting: no IoT or after-count ≤ 0 → no accuracy.
|
||
* Otherwise min(IoT, after) / max(IoT, after) × 100, 1 decimal.
|
||
*/
|
||
export function countingAccuracy(
|
||
iotCount: number | null | undefined,
|
||
afterCount: number
|
||
): number | null {
|
||
if (!isPresentNumber(iotCount) || afterCount <= 0) return null;
|
||
return minMaxPercent(iotCount, afterCount);
|
||
}
|
||
|
||
/**
|
||
* Sacks: both 0 (missing counts treated as 0) → 100%.
|
||
* Only one side > 0 → N/A. Both > 0 → min/max × 100.
|
||
*/
|
||
export function sackAccuracy(
|
||
manual: number | null | undefined,
|
||
iot: number | null | undefined
|
||
): number | null {
|
||
const manualVal = isPresentNumber(manual) ? manual : 0;
|
||
const iotVal = isPresentNumber(iot) ? iot : 0;
|
||
if (manualVal === 0 && iotVal === 0) return 100;
|
||
if (manualVal === 0 || iotVal === 0) return null;
|
||
return minMaxPercent(manualVal, iotVal);
|
||
}
|
||
|
||
/**
|
||
* Weight: same smaller/larger rule on average IoT vs average manual.
|
||
* Missing or non-positive averages → no accuracy.
|
||
*/
|
||
export function weightAccuracy(
|
||
manualAvg: number | null | undefined,
|
||
iotAvg: number | null | undefined
|
||
): number | null {
|
||
if (!isPresentNumber(manualAvg) || !isPresentNumber(iotAvg)) return null;
|
||
if (manualAvg <= 0 || iotAvg <= 0) return null;
|
||
return minMaxPercent(manualAvg, iotAvg);
|
||
}
|
||
|
||
/** Mortality column is mati + afkir. Panen is ignored. */
|
||
export function combinedMortality(mati: number, afkir: number, _panen?: number): number {
|
||
return (mati || 0) + (afkir || 0);
|
||
}
|