From 1217f34dcd354a9923c8f8a9fbdfb5fecf3583f8 Mon Sep 17 00:00:00 2001 From: ragilap Date: Sun, 11 Jan 2026 22:19:20 +0700 Subject: [PATCH] feat(BE):change standart egg in fcr master data --- .../dashboard_stats.repository.go | 113 +++++++--- .../dashboards/services/dashboard.service.go | 199 +++++++++++++++--- 2 files changed, 254 insertions(+), 58 deletions(-) diff --git a/internal/modules/dashboards/repositories/dashboard_stats.repository.go b/internal/modules/dashboards/repositories/dashboard_stats.repository.go index 948c1b56..7582680b 100644 --- a/internal/modules/dashboards/repositories/dashboard_stats.repository.go +++ b/internal/modules/dashboards/repositories/dashboard_stats.repository.go @@ -188,39 +188,92 @@ func (r *DashboardRepositoryImpl) GetStandardFcrWeekly(ctx context.Context, week return nil, nil } - source := r.standardSourceSubquery(filters) - if source == nil { - return nil, nil + filterClause := "" + filterArgs := make([]interface{}, 0) + if filters != nil { + if len(filters.FlockIds) > 0 { + filterClause += " AND pf.id IN ?" + filterArgs = append(filterArgs, filters.FlockIds) + } + if len(filters.KandangIds) > 0 { + filterClause += " AND k.id IN ?" + filterArgs = append(filterArgs, filters.KandangIds) + } + if len(filters.LokasiIds) > 0 { + filterClause += " AND k.location_id IN ?" + filterArgs = append(filterArgs, filters.LokasiIds) + } } - var rows []StandardWeeklyFcrMetric - db := r.DB().WithContext(ctx). - Table("standard_growth_details AS sgd"). - Select(` - sgd.week AS week, - COALESCE(AVG( - COALESCE( - ( - SELECT fs.fcr_number - FROM fcr_standards fs - WHERE fs.fcr_id = src.fcr_id - AND fs.weight >= CASE WHEN sgd.target_mean_bw > 10 THEN sgd.target_mean_bw / 1000 ELSE sgd.target_mean_bw END - ORDER BY fs.weight ASC - LIMIT 1 - ), - ( - SELECT fs.fcr_number - FROM fcr_standards fs - WHERE fs.fcr_id = src.fcr_id - ORDER BY fs.weight DESC - LIMIT 1 - ) - ) - ), 0) AS std_fcr`). - Joins("JOIN (?) AS src ON src.production_standard_id = sgd.production_standard_id", source). - Where("sgd.week IN ?", weeks) + query := fmt.Sprintf(` +WITH src AS ( + SELECT DISTINCT pf.production_standard_id, pf.fcr_id + FROM project_flocks pf + JOIN project_flock_kandangs pfk ON pfk.project_flock_id = pf.id + JOIN kandangs k ON k.id = pfk.kandang_id + WHERE pf.production_standard_id > 0 AND pf.fcr_id > 0 + %s +), +actual AS ( + SELECT u.week AS week, + pf.fcr_id AS fcr_id, + AVG((u.chart_data->'statistics'->>'average_weight')::numeric) AS avg_weight + FROM project_flock_kandang_uniformity u + JOIN project_flock_kandangs pfk ON pfk.id = u.project_flock_kandang_id + JOIN project_flocks pf ON pf.id = pfk.project_flock_id + JOIN kandangs k ON k.id = pfk.kandang_id + WHERE u.week IN ? AND u.uniform_date IS NOT NULL AND pf.fcr_id > 0 + %s + GROUP BY u.week, pf.fcr_id +), +target AS ( + SELECT sgd.week AS week, + src.fcr_id AS fcr_id, + AVG(sgd.target_mean_bw) AS target_mean_bw + FROM standard_growth_details sgd + JOIN src ON src.production_standard_id = sgd.production_standard_id + WHERE sgd.week IN ? + GROUP BY sgd.week, src.fcr_id +), +weights AS ( + SELECT COALESCE(a.week, t.week) AS week, + COALESCE(a.fcr_id, t.fcr_id) AS fcr_id, + COALESCE( + CASE WHEN a.avg_weight > 10 THEN a.avg_weight / 1000 ELSE a.avg_weight END, + CASE WHEN t.target_mean_bw > 10 THEN t.target_mean_bw / 1000 ELSE t.target_mean_bw END + ) AS weight + FROM actual a + FULL OUTER JOIN target t ON t.week = a.week AND t.fcr_id = a.fcr_id +) +SELECT w.week AS week, + COALESCE(AVG( + COALESCE( + (SELECT fs.fcr_number + FROM fcr_standards fs + WHERE fs.fcr_id = w.fcr_id + AND fs.weight >= w.weight + ORDER BY fs.weight ASC + LIMIT 1), + (SELECT fs.fcr_number + FROM fcr_standards fs + WHERE fs.fcr_id = w.fcr_id + ORDER BY fs.weight DESC + LIMIT 1) + ) + ), 0) AS std_fcr +FROM weights w +GROUP BY w.week +ORDER BY w.week ASC +`, filterClause, filterClause) - if err := db.Group("sgd.week").Order("sgd.week ASC").Scan(&rows).Error; err != nil { + args := make([]interface{}, 0, len(filterArgs)*2+2) + args = append(args, filterArgs...) + args = append(args, weeks) + args = append(args, filterArgs...) + args = append(args, weeks) + + var rows []StandardWeeklyFcrMetric + if err := r.DB().WithContext(ctx).Raw(query, args...).Scan(&rows).Error; err != nil { return nil, err } diff --git a/internal/modules/dashboards/services/dashboard.service.go b/internal/modules/dashboards/services/dashboard.service.go index 7c083c98..f8d532f0 100644 --- a/internal/modules/dashboards/services/dashboard.service.go +++ b/internal/modules/dashboards/services/dashboard.service.go @@ -507,37 +507,180 @@ func (s dashboardService) buildComparisonChartsAll(ctx context.Context, startDat stdFcr[row.Week] = row.StdFcr } - layingWeeks, layingActual := mapComparisonWeeklyMetricRows(layingRows) - eggWeightWeeks, eggWeightActual := mapComparisonWeeklyMetricRows(eggWeightRows) - feedWeeks, feedActual := mapComparisonWeeklyMetricRows(feedIntakeRows) - fcrWeeks, fcrActual := mapComparisonWeeklyMetricRows(fcrRows) - deplesiWeeks, deplesiActual := mapComparisonWeeklyMetricRows(deplesiRows) - bodyWeightWeeks, bodyWeightActual, uniformityWeeks, uniformityActual := mapComparisonUniformityRows(uniformityRows) + _, layingActual := mapComparisonWeeklyMetricRows(layingRows) + _, eggWeightActual := mapComparisonWeeklyMetricRows(eggWeightRows) + _, feedActual := mapComparisonWeeklyMetricRows(feedIntakeRows) + _, fcrActual := mapComparisonWeeklyMetricRows(fcrRows) + _, deplesiActual := mapComparisonWeeklyMetricRows(deplesiRows) + _, bodyWeightActual, _, uniformityActual := mapComparisonUniformityRows(uniformityRows) - charts := map[string]dto.DashboardChartDTO{} - if len(bodyWeightWeeks) > 0 { - charts["body_weight"] = buildComparisonPercentChart(seriesRows, bodyWeightWeeks, bodyWeightActual, stdBodyWeight) - } - if len(layingWeeks) > 0 { - charts["laying"] = buildComparisonPercentChart(seriesRows, layingWeeks, layingActual, stdLaying) - } - if len(eggWeightWeeks) > 0 { - charts["egg_weight"] = buildComparisonPercentChart(seriesRows, eggWeightWeeks, eggWeightActual, stdEggWeight) - } - if len(feedWeeks) > 0 { - charts["feed_intake"] = buildComparisonPercentChart(seriesRows, feedWeeks, feedActual, stdFeedIntake) - } - if len(uniformityWeeks) > 0 { - charts["uniformity"] = buildComparisonPercentChart(seriesRows, uniformityWeeks, uniformityActual, stdUniformity) - } - if len(fcrWeeks) > 0 { - charts["fcr"] = buildComparisonPercentChart(seriesRows, fcrWeeks, fcrActual, stdFcr) - } - if len(deplesiWeeks) > 0 { - charts["deplesi"] = buildComparisonPercentChart(seriesRows, deplesiWeeks, deplesiActual, stdDeplesi) + aggregateActual := buildAggregateComparisonPercent(weeks, seriesRows, aggregateComparisonInput{ + BodyWeightActual: bodyWeightActual, + LayingActual: layingActual, + EggWeightActual: eggWeightActual, + FeedIntakeActual: feedActual, + UniformityActual: uniformityActual, + FcrActual: fcrActual, + DeplesiActual: deplesiActual, + StdBodyWeight: stdBodyWeight, + StdLaying: stdLaying, + StdEggWeight: stdEggWeight, + StdFeedIntake: stdFeedIntake, + StdUniformity: stdUniformity, + StdFcr: stdFcr, + StdDeplesi: stdDeplesi, + }) + + if len(aggregateActual) == 0 { + return map[string]dto.DashboardChartDTO{}, nil } - return charts, nil + chartKey := strings.ToLower(params.ComparisonType) + return map[string]dto.DashboardChartDTO{ + chartKey: buildComparisonAggregateChart(seriesRows, weeks, aggregateActual), + }, nil +} + +type aggregateComparisonInput struct { + BodyWeightActual map[int]map[uint]float64 + LayingActual map[int]map[uint]float64 + EggWeightActual map[int]map[uint]float64 + FeedIntakeActual map[int]map[uint]float64 + UniformityActual map[int]map[uint]float64 + FcrActual map[int]map[uint]float64 + DeplesiActual map[int]map[uint]float64 + StdBodyWeight map[int]float64 + StdLaying map[int]float64 + StdEggWeight map[int]float64 + StdFeedIntake map[int]float64 + StdUniformity map[int]float64 + StdFcr map[int]float64 + StdDeplesi map[int]float64 +} + +func buildAggregateComparisonPercent(weeks []int, seriesRows []repository.ComparisonSeries, input aggregateComparisonInput) map[int]map[uint]float64 { + result := map[int]map[uint]float64{} + + for _, week := range weeks { + stdBodyWeight := input.StdBodyWeight[week] + stdLaying := input.StdLaying[week] + stdEggWeight := input.StdEggWeight[week] + stdFeedIntake := input.StdFeedIntake[week] + stdUniformity := input.StdUniformity[week] + stdFcr := input.StdFcr[week] + stdDeplesi := input.StdDeplesi[week] + + for _, series := range seriesRows { + sum := 0.0 + count := 0.0 + + if percent, ok := higherIsBetterPercent(input.LayingActual, week, series.Id, stdLaying); ok { + sum += percent + count++ + } + if percent, ok := higherIsBetterPercent(input.EggWeightActual, week, series.Id, stdEggWeight); ok { + sum += percent + count++ + } + if percent, ok := higherIsBetterPercent(input.UniformityActual, week, series.Id, stdUniformity); ok { + sum += percent + count++ + } + if percent, ok := lowerIsBetterPercent(input.FcrActual, week, series.Id, stdFcr); ok { + sum += percent + count++ + } + if percent, ok := lowerIsBetterPercent(input.DeplesiActual, week, series.Id, stdDeplesi); ok { + sum += percent + count++ + } + if percent, ok := higherIsBetterPercent(input.BodyWeightActual, week, series.Id, stdBodyWeight); ok { + sum += percent + count++ + } + if percent, ok := lowerIsBetterPercent(input.FeedIntakeActual, week, series.Id, stdFeedIntake); ok { + sum += percent + count++ + } + + if count == 0 { + continue + } + + if result[week] == nil { + result[week] = map[uint]float64{} + } + result[week][series.Id] = sum / count + } + } + + return result +} + +func higherIsBetterPercent(actual map[int]map[uint]float64, week int, seriesId uint, standard float64) (float64, bool) { + if standard <= 0 { + return 0, false + } + val, ok := metricValue(actual, week, seriesId) + if !ok || val <= 0 { + return 0, false + } + return (val / standard) * 100, true +} + +func lowerIsBetterPercent(actual map[int]map[uint]float64, week int, seriesId uint, standard float64) (float64, bool) { + if standard <= 0 { + return 0, false + } + val, ok := metricValue(actual, week, seriesId) + if !ok || val <= 0 { + return 0, false + } + return (standard / val) * 100, true +} + +func metricValue(actual map[int]map[uint]float64, week int, seriesId uint) (float64, bool) { + weekRows, ok := actual[week] + if !ok { + return 0, false + } + val, ok := weekRows[seriesId] + return val, ok +} + +func buildComparisonAggregateChart(seriesRows []repository.ComparisonSeries, weeks []int, actual map[int]map[uint]float64) dto.DashboardChartDTO { + series := make([]dto.DashboardChartSeriesDTO, 0, len(seriesRows)) + for _, sRow := range seriesRows { + series = append(series, dto.DashboardChartSeriesDTO{ + Id: strconv.FormatUint(uint64(sRow.Id), 10), + Label: sRow.Label, + Unit: "%", + }) + } + + dataset := make([]map[string]interface{}, 0, len(weeks)) + for _, week := range weeks { + row := map[string]interface{}{ + "week": week, + } + values, ok := actual[week] + if !ok { + continue + } + for _, sRow := range seriesRows { + if val, exists := values[sRow.Id]; exists { + row[strconv.FormatUint(uint64(sRow.Id), 10)] = roundTo(val, 2) + } + } + if len(row) > 1 { + dataset = append(dataset, row) + } + } + + return dto.DashboardChartDTO{ + Series: series, + Dataset: dataset, + } } func (s dashboardService) standardComparisonMap(ctx context.Context, weeks []int, metric string, filter *validation.DashboardFilter) (map[int]float64, error) {