Add dashboard report snapshots feature with CRUD operations. Implemented new database migrations for dashboard_report_snapshots, including report type handling. Updated backend routes and models to support snapshot retrieval and upsert functionality. Enhanced frontend components to integrate report snapshot management, including UI for daily and end-cycle reports. Added tests for new functionality to ensure reliability.
CI / lint-and-test (push) Canceled after 0s
CI / lint-and-test (push) Canceled after 0s
This commit is contained in:
1 parent
059087be1d
commit
34336b9bd1
41 files changed
+4824
-178
No files matched your search
@@ -0,0 +1,69 @@
|
||||
import type { DashboardReportEefRow, DashboardReportFcrRow } from '../types/dashboardReport.ts';
|
||||
|
||||
type ScalesKpiRow = {
|
||||
food_total?: number;
|
||||
iot_weight?: number;
|
||||
chicken_life?: number;
|
||||
harvest_weight_total?: number;
|
||||
iot_fcr?: number;
|
||||
chicken_life_percentage?: number;
|
||||
iot_average_harvest_bw?: number;
|
||||
iot_average_day_weight?: number;
|
||||
iot_eef?: number;
|
||||
};
|
||||
|
||||
const toValidRatio = (value: number | undefined): number | null =>
|
||||
value !== undefined && value > 0 ? value : null;
|
||||
|
||||
const toNullableNumber = (value: number | undefined | null): number | null =>
|
||||
value !== undefined && value !== null ? value : null;
|
||||
|
||||
const isKpiMap = (value: unknown): value is Record<string, ScalesKpiRow> =>
|
||||
!!value && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const kpiDaysInRange = (
|
||||
kpi: unknown,
|
||||
upToDay: number
|
||||
): Array<[number, ScalesKpiRow]> => {
|
||||
if (!isKpiMap(kpi)) return [];
|
||||
return Object.entries(kpi)
|
||||
.map(([dayKey, row]) => [Number(dayKey), row || {}] as [number, ScalesKpiRow])
|
||||
.filter(([day]) => Number.isFinite(day) && day >= 0 && day <= upToDay)
|
||||
.sort((a, b) => a[0] - b[0]);
|
||||
};
|
||||
|
||||
export function mapKpiToFcrRows(kpi: unknown, upToDay: number): DashboardReportFcrRow[] {
|
||||
return kpiDaysInRange(kpi, upToDay).map(([day, row]) => ({
|
||||
day,
|
||||
feedTotal: row.food_total ?? 0,
|
||||
iotWeight: toNullableNumber(row.iot_weight),
|
||||
chickenLife: row.chicken_life ?? 0,
|
||||
harvestWeightTotal: row.harvest_weight_total ?? 0,
|
||||
iotFcr: toValidRatio(row.iot_fcr),
|
||||
}));
|
||||
}
|
||||
|
||||
export function mapKpiToEefRows(kpi: unknown, upToDay: number): DashboardReportEefRow[] {
|
||||
return kpiDaysInRange(kpi, upToDay).map(([day, row]) => ({
|
||||
day,
|
||||
chickenLifePercentage: toNullableNumber(row.chicken_life_percentage),
|
||||
iotAverageHarvestBw: toNullableNumber(row.iot_average_harvest_bw),
|
||||
iotAverageDayWeight: toNullableNumber(row.iot_average_day_weight),
|
||||
iotFcr: toValidRatio(row.iot_fcr),
|
||||
iotEef: toValidRatio(row.iot_eef),
|
||||
}));
|
||||
}
|
||||
|
||||
export function latestValidRatio<T>(
|
||||
rows: T[] | null | undefined,
|
||||
key: keyof T
|
||||
): number | null {
|
||||
if (!rows?.length) return null;
|
||||
for (let i = rows.length - 1; i >= 0; i--) {
|
||||
const value = rows[i]?.[key];
|
||||
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in new issue
Block a user