update ai insight migration for all page

This commit is contained in:
Alberto-Audrix committed 2026-09-17 10:11:14 +07:00
1 parent 07d36081ce
commit 18790ab8f6
69 files changed
+9457 -399

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
/**
* Environment standards from the CP 707 book, for client-side charting only.
*
* Mirrors temperature / air-quality tables in
* `backend/apps/operations/services/cp707_knowledge.py` — that Python module
* is authoritative for LLM grading. This TS copy exists so IoT charts can
* draw targets without a round trip. Keep both in sync when the book edition
* changes.
*/
/** Target suhu pemeliharaan per umur (buku CP 707). */
const TEMPERATURE_STANDARD = [
{ from: 1, to: 2, temp_C: 32 },
{ from: 3, to: 4, temp_C: 31 },
{ from: 5, to: 7, temp_C: 30 },
{ from: 8, to: 14, temp_C: 29 },
{ from: 15, to: 21, temp_C: 28 },
{ from: 22, to: 28, temp_C: 26 },
{ from: 29, to: 35, temp_C: 23 },
{ from: 36, to: 99, temp_C: 22 },
];
/** Kelembapan ideal sama untuk semua umur menurut tabel buku: 50–70 %. */
export const HUMIDITY_RANGE_PCT = { min: 50, max: 70 };
/** Standar kualitas udara (Lampiran 3). */
export const AIR_QUALITY = {
/** Amonia: ideal <10 ppm, batas kritis >25 ppm. */
ammoniaSafe_ppm: 10,
ammoniaCritical_ppm: 25,
/** CO2: ideal <3000 ppm, kritis >3500 ppm. */
co2Ideal_ppm: 3000,
};
/**
* Standar konsumsi air per 1000 ekor per hari pada suhu 21°C (buku CP 707).
* Di atas 21°C kebutuhan naik rata-rata 6.5% per derajat.
*/
const WATER_CONSUMPTION_STANDARD: { week: number; minLiter: number; maxLiter: number }[] = [
{ week: 1, minLiter: 58, maxLiter: 65 },
{ week: 2, minLiter: 102, maxLiter: 115 },
{ week: 3, minLiter: 149, maxLiter: 167 },
{ week: 4, minLiter: 192, maxLiter: 216 },
{ week: 5, minLiter: 232, maxLiter: 261 },
{ week: 6, minLiter: 274, maxLiter: 308 },
{ week: 7, minLiter: 309, maxLiter: 347 },
{ week: 8, minLiter: 342, maxLiter: 385 },
];
/** Standar konsumsi air (rata-rata min–max) untuk umur hari tertentu, atau null. */
export const getWaterStandardByDay = (
dayAge: number | null | undefined
): { min: number; max: number } | null => {
if (typeof dayAge !== 'number' || !Number.isFinite(dayAge) || dayAge <= 0) return null;
const week = Math.min(Math.ceil(dayAge / 7), WATER_CONSUMPTION_STANDARD.length);
const row = WATER_CONSUMPTION_STANDARD.find((r) => r.week === week);
return row ? { min: row.minLiter, max: row.maxLiter } : null;
};
/**
* Suhu target untuk umur tertentu, atau null bila umurnya tidak masuk akal.
* Umur di luar tabel jatuh ke baris terakhir (36+ hari), sesuai isi buku.
*/
export const getTempStandardByDay = (dayAge: number | null | undefined): number | null => {
if (typeof dayAge !== 'number' || !Number.isFinite(dayAge) || dayAge <= 0) return null;
const row = TEMPERATURE_STANDARD.find((r) => dayAge >= r.from && dayAge <= r.to);
return row ? row.temp_C : null;
};
/**
* Number out of a display-formatted reading.
*
* `computeDashboard` hands back strings like "32,1 °C" and "62,8 %" — already
* scaled from the raw sensor encoding, which is why they are the safe source —
* so the chart has to strip the unit and accept the Indonesian decimal comma.
* A plain number passes through unchanged.
*/
export const parseReading = (value: unknown): number | null => {
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
if (typeof value !== 'string' || value.trim() === '') return null;
const cleaned = value
.replace(/[^\d,.-]/g, '')
.replace(/\.(?=\d{3}\b)/g, '')
.replace(',', '.');
const n = Number(cleaned);
return Number.isFinite(n) ? n : null;
};