Files
dashboard-cpsp/utils/insightHelpers.ts
T

202 lines
5.5 KiB
TypeScript

import { cobbStandardPerformance } from '../mockData/cobbStandard.ts';
/**
* Analyze trend of numerical data over a window
* @param data Array of numerical values
* @param windowSize Number of recent data points to analyze
* @returns Trend description in Indonesian
*/
export function analyzeTrend(data: number[], windowSize = 7): string {
if (!data || data.length < 2) {
return 'tidak cukup data';
}
// Filter out invalid values (null, undefined, NaN, 0)
const validData = data.filter((val) => val != null && !isNaN(val) && val !== 0);
if (validData.length < 2) {
return 'tidak cukup data';
}
// Take last N values
const recentData = validData.slice(-Math.min(windowSize, validData.length));
if (recentData.length < 2) {
return 'tidak cukup data';
}
// Calculate simple linear regression slope
const n = recentData.length;
const xMean = (n - 1) / 2; // Mean of indices 0, 1, 2, ..., n-1
const yMean = recentData.reduce((sum, val) => sum + val, 0) / n;
let numerator = 0;
let denominator = 0;
for (let i = 0; i < n; i++) {
numerator += (i - xMean) * ((recentData[i] ?? 0) - yMean);
denominator += Math.pow(i - xMean, 2);
}
const slope = denominator !== 0 ? numerator / denominator : 0;
// Calculate relative slope (as percentage of mean)
const relativeSlope = yMean !== 0 ? (slope / yMean) * 100 : 0;
// Determine trend based on relative slope
// Threshold: >2% = increasing, <-2% = decreasing, else stable
if (relativeSlope > 2) {
return 'meningkat';
} else if (relativeSlope < -2) {
return 'menurun';
} else {
return 'stabil';
}
}
/**
* Get cycle phase based on day of cycle
* @param day Current day of cycle (1-based)
* @returns Phase name
*/
export function getCyclePhase(day: number): string {
if (day <= 14) {
return 'Brooding';
} else if (day <= 28) {
return 'Growth';
} else {
return 'Finisher';
}
}
/**
* Get Cobb standard performance metrics for a given day
* @param day Day of cycle (1-based)
* @returns Object with FCR and weight, or null if not available
*/
export function getCobbStandardForDay(day: number): { fcr: number; weight: number } | null {
const standard = cobbStandardPerformance.find((entry) => entry.day === day);
if (standard) {
return {
fcr: standard.fcr,
// Weight targets live in page builders / CP707 knowledge; FCR row has no weight column here.
weight: 0,
};
}
return null;
}
/**
* Calculate deviation between actual and target values
* @param actual Actual value
* @param target Target value
* @returns Object with absolute and percentage deviation
*/
export function calculateDeviation(
actual: number,
target: number
): { absolute: number; percent: number } {
const absolute = actual - target;
const percent = target !== 0 ? (absolute / target) * 100 : 0;
return { absolute, percent };
}
/**
* Determine environmental status based on readings and cycle phase
* @param temp Temperature in Celsius
* @param humidity Humidity percentage
* @param ammonia Ammonia in ppm
* @param day Current day of cycle
* @returns Status description in Indonesian
*/
export function getEnvironmentalStatus(
temp: number,
humidity: number,
ammonia: number,
day: number
): string {
const issues: string[] = [];
// Age-appropriate temperature ranges
// Younger chicks need warmer temperatures
let optimalTempMin: number;
let optimalTempMax: number;
if (day <= 7) {
optimalTempMin = 32;
optimalTempMax = 35;
} else if (day <= 14) {
optimalTempMin = 30;
optimalTempMax = 32;
} else if (day <= 21) {
optimalTempMin = 28;
optimalTempMax = 30;
} else {
optimalTempMin = 26;
optimalTempMax = 28;
}
// Check temperature
if (temp < optimalTempMin) {
issues.push('suhu terlalu rendah');
} else if (temp > optimalTempMax) {
issues.push('suhu terlalu tinggi');
}
// Check humidity (optimal: 50-70%)
if (humidity < 50) {
issues.push('kelembapan rendah');
} else if (humidity > 70) {
issues.push('kelembapan tinggi');
}
// Check ammonia (safe: <25ppm, warning: 25-50ppm, critical: >50ppm)
if (ammonia > 50) {
issues.push('amonia kritis');
} else if (ammonia > 25) {
issues.push('amonia tinggi');
}
if (issues.length === 0) {
return 'optimal';
} else if (issues.length === 1) {
return issues[0] ?? 'optimal';
} else {
return issues.join(', ');
}
}
/**
* Analyze feed consumption trend
* @param feedHistory Array of daily feed consumption (in sacks)
* @param _currentDay Current day of cycle (reserved for future use)
* @returns Trend description in Indonesian
*/
export function analyzeFeedTrend(feedHistory: number[], _currentDay: number): string {
if (!feedHistory || feedHistory.length < 3) {
return 'tidak cukup data';
}
// Get last 7 days of feed data
const recentFeed = feedHistory.slice(-7);
// Rough expectation: feed consumption should increase gradually as chickens grow
// Early days: 0.5-1 sack/1000 birds/day
// Mid cycle: 1-2 sacks/1000 birds/day
// Late cycle: 2-3 sacks/1000 birds/day
// This is a simplified heuristic - in real deployment, this would be calibrated
// based on actual farm data and population size
// For now, just analyze if consumption is increasing (expected as birds grow)
const trend = analyzeTrend(recentFeed, 7);
if (trend === 'meningkat') {
return 'sesuai pertumbuhan';
} else if (trend === 'menurun') {
return 'menurun (perlu perhatian)';
} else {
return 'stabil';
}
}