update applying current ai insight from dashboard-cpsp
This commit is contained in:
1 parent
31272035e9
commit
b87f20e3b3
27 files changed
+5300
-343
No files matched your search
@@ -0,0 +1,201 @@
|
||||
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';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user