import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class StatisticsService { constructor(private readonly prisma: PrismaService) {} async colonies() { const [byStatus, strengthOverTime, losses] = await Promise.all([ this.prisma.colony.groupBy({ by: ['status'], _count: { _all: true } }), this.prisma.inspection.findMany({ where: { colonyStrength: { not: null } }, select: { colonyId: true, date: true, colonyStrength: true }, orderBy: { date: 'asc' }, }), this.prisma.colony.findMany({ where: { status: { in: ['DECEASED', 'DISSOLVED'] } }, select: { deactivatedAt: true, updatedAt: true }, }), ]); const lossesByYear: Record = {}; for (const loss of losses) { const year = (loss.deactivatedAt ?? loss.updatedAt) .getFullYear() .toString(); lossesByYear[year] = (lossesByYear[year] ?? 0) + 1; } return { byStatus: byStatus.map((row) => ({ status: row.status, count: row._count._all, })), strengthOverTime, lossesByYear, }; } async queens() { const [queens, evaluations] = await Promise.all([ this.prisma.queen.findMany({ select: { id: true, code: true, status: true, createdAt: true, deactivatedAt: true, }, }), this.prisma.queenEvaluation.findMany({ select: { queenId: true, colonyStrength: true, gentleness: true, combBuild: true, swarmingTendency: true, broodPattern: true, honeyYield: true, }, }), ]); const retiredQueens = queens.filter( (queen) => queen.deactivatedAt !== null, ); const averageLifespanDays = retiredQueens.length > 0 ? retiredQueens.reduce((sum, queen) => { const days = (queen.deactivatedAt!.getTime() - queen.createdAt.getTime()) / (1000 * 60 * 60 * 24); return sum + days; }, 0) / retiredQueens.length : null; const byQueen = new Map(); for (const evaluation of evaluations) { const bucket = byQueen.get(evaluation.queenId) ?? []; bucket.push(evaluation); byQueen.set(evaluation.queenId, bucket); } function average(values: number[]): number { return values.reduce((sum, value) => sum + value, 0) / values.length; } const evaluationAverages = queens .filter((queen) => byQueen.has(queen.id)) .map((queen) => { const rows = byQueen.get(queen.id)!; return { queenId: queen.id, code: queen.code, evaluationCount: rows.length, colonyStrength: average(rows.map((row) => row.colonyStrength)), gentleness: average(rows.map((row) => row.gentleness)), combBuild: average(rows.map((row) => row.combBuild)), swarmingTendency: average(rows.map((row) => row.swarmingTendency)), broodPattern: average(rows.map((row) => row.broodPattern)), honeyYield: average(rows.map((row) => row.honeyYield)), }; }) .sort((a, b) => b.honeyYield - a.honeyYield); return { totalActive: queens.filter((queen) => queen.status === 'ACTIVE').length, averageLifespanDays, evaluationAverages, }; } async apiaries() { const apiaries = await this.prisma.apiary.findMany({ select: { id: true, name: true, colonies: { select: { id: true, status: true } }, harvests: { select: { netWeight: true } }, }, }); return apiaries.map((apiary) => { const totalHoneyKg = apiary.harvests.reduce( (sum, harvest) => sum + (harvest.netWeight ?? 0), 0, ); const activeColonies = apiary.colonies.filter( (c) => c.status === 'ACTIVE', ).length; const lostColonies = apiary.colonies.filter((c) => ['DECEASED', 'DISSOLVED'].includes(c.status), ).length; return { apiaryId: apiary.id, name: apiary.name, activeColonies, lostColonies, totalHoneyKg, averageHoneyKgPerColony: apiary.colonies.length > 0 ? totalHoneyKg / apiary.colonies.length : 0, }; }); } async varroa() { const measurements = await this.prisma.varroaMeasurement.findMany({ where: { miteCount: { not: null } }, select: { colonyId: true, date: true, miteCount: true, method: true }, orderBy: { date: 'asc' }, }); const treatmentsByColony = await this.prisma.treatment.groupBy({ by: ['colonyId'], _count: { _all: true }, }); return { measurements, treatmentCountByColony: treatmentsByColony.map((row) => ({ colonyId: row.colonyId, treatmentCount: row._count._all, })), }; } async honey() { const [harvests, honeyBatches] = await Promise.all([ this.prisma.harvest.findMany({ select: { apiaryId: true, date: true, netWeight: true }, }), this.prisma.honeyBatch.findMany({ select: { amount: true, createdAt: true }, }), ]); const totalHarvestKg = harvests.reduce( (sum, h) => sum + (h.netWeight ?? 0), 0, ); const totalHoneyBatchKg = honeyBatches.reduce( (sum, b) => sum + (b.amount ?? 0), 0, ); const yieldPerYear: Record = {}; const yieldPerApiary: Record = {}; for (const harvest of harvests) { const year = harvest.date.getFullYear().toString(); yieldPerYear[year] = (yieldPerYear[year] ?? 0) + (harvest.netWeight ?? 0); yieldPerApiary[harvest.apiaryId] = (yieldPerApiary[harvest.apiaryId] ?? 0) + (harvest.netWeight ?? 0); } return { totalHarvestKg, totalHoneyBatchKg, yieldPerYear, yieldPerApiary, }; } async feeding() { const feedings = await this.prisma.feeding.findMany({ select: { colonyId: true, date: true, amount: true, unit: true }, }); const byColony: Record = {}; const bySeason: Record = {}; for (const feeding of feedings) { byColony[feeding.colonyId] = (byColony[feeding.colonyId] ?? 0) + feeding.amount; const year = feeding.date.getFullYear().toString(); bySeason[year] = (bySeason[year] ?? 0) + feeding.amount; } return { byColony, bySeason }; } }