feat(backend): Statistik- und Reporting-Endpunkte (Modul 27)

Aggregations-Endpunkte fuer Voelker, Koeniginnen, Bienenstaende, Varroa,
Honig und Fuetterung nach Lastenheft Abschnitt 4.26.
This commit is contained in:
Nick Szaunig committed 2026-08-23 06:33:50 +02:00
1 parent 8080a7f5ec
commit e8177432b0
6 files changed
+706

No files matched your search

@@ -0,0 +1,216 @@
import { StatisticsService } from './statistics.service';
import { PrismaService } from '../prisma/prisma.service';
describe('StatisticsService', () => {
let service: StatisticsService;
let prisma: {
colony: { groupBy: jest.Mock; findMany: jest.Mock };
inspection: { findMany: jest.Mock };
queen: { findMany: jest.Mock };
queenEvaluation: { findMany: jest.Mock };
apiary: { findMany: jest.Mock };
varroaMeasurement: { findMany: jest.Mock };
treatment: { groupBy: jest.Mock };
harvest: { findMany: jest.Mock };
honeyBatch: { findMany: jest.Mock };
feeding: { findMany: jest.Mock };
};
beforeEach(() => {
prisma = {
colony: {
groupBy: jest.fn().mockResolvedValue([]),
findMany: jest.fn().mockResolvedValue([]),
},
inspection: { findMany: jest.fn().mockResolvedValue([]) },
queen: { findMany: jest.fn().mockResolvedValue([]) },
queenEvaluation: { findMany: jest.fn().mockResolvedValue([]) },
apiary: { findMany: jest.fn().mockResolvedValue([]) },
varroaMeasurement: { findMany: jest.fn().mockResolvedValue([]) },
treatment: { groupBy: jest.fn().mockResolvedValue([]) },
harvest: { findMany: jest.fn().mockResolvedValue([]) },
honeyBatch: { findMany: jest.fn().mockResolvedValue([]) },
feeding: { findMany: jest.fn().mockResolvedValue([]) },
};
service = new StatisticsService(prisma as unknown as PrismaService);
});
describe('colonies', () => {
it('groups losses by year using deactivatedAt, falling back to updatedAt', async () => {
prisma.colony.groupBy.mockResolvedValueOnce([
{ status: 'ACTIVE', _count: { _all: 5 } },
]);
prisma.colony.findMany.mockResolvedValueOnce([
{
deactivatedAt: new Date('2025-09-01'),
updatedAt: new Date('2025-09-01'),
},
{ deactivatedAt: null, updatedAt: new Date('2024-03-01') },
]);
const result = await service.colonies();
expect(result.byStatus).toEqual([{ status: 'ACTIVE', count: 5 }]);
expect(result.lossesByYear).toEqual({ '2025': 1, '2024': 1 });
});
});
describe('queens', () => {
it('averages evaluation dimensions per queen and sorts by honey yield descending', async () => {
prisma.queen.findMany.mockResolvedValueOnce([
{
id: 'q1',
code: 'K-001',
status: 'ACTIVE',
createdAt: new Date('2024-01-01'),
deactivatedAt: null,
},
{
id: 'q2',
code: 'K-002',
status: 'ACTIVE',
createdAt: new Date('2024-01-01'),
deactivatedAt: null,
},
]);
prisma.queenEvaluation.findMany.mockResolvedValueOnce([
{
queenId: 'q1',
colonyStrength: 3,
gentleness: 3,
combBuild: 3,
swarmingTendency: 3,
broodPattern: 3,
honeyYield: 2,
},
{
queenId: 'q2',
colonyStrength: 5,
gentleness: 5,
combBuild: 5,
swarmingTendency: 5,
broodPattern: 5,
honeyYield: 5,
},
]);
const result = await service.queens();
expect(result.evaluationAverages.map((row) => row.queenId)).toEqual([
'q2',
'q1',
]);
expect(result.evaluationAverages[0].honeyYield).toBe(5);
});
it('computes average lifespan only from retired queens', async () => {
prisma.queen.findMany.mockResolvedValueOnce([
{
id: 'q1',
code: 'K-001',
status: 'RETIRED',
createdAt: new Date('2024-01-01'),
deactivatedAt: new Date('2024-01-11'),
},
{
id: 'q2',
code: 'K-002',
status: 'ACTIVE',
createdAt: new Date('2024-01-01'),
deactivatedAt: null,
},
]);
const result = await service.queens();
expect(result.averageLifespanDays).toBe(10);
});
});
describe('apiaries', () => {
it('computes honey totals and colony counts per apiary', async () => {
prisma.apiary.findMany.mockResolvedValueOnce([
{
id: 'a1',
name: 'Wabenhain Nord',
colonies: [
{ id: 'c1', status: 'ACTIVE' },
{ id: 'c2', status: 'DECEASED' },
],
harvests: [{ netWeight: 10 }, { netWeight: 5 }],
},
]);
const result = await service.apiaries();
expect(result).toEqual([
{
apiaryId: 'a1',
name: 'Wabenhain Nord',
activeColonies: 1,
lostColonies: 1,
totalHoneyKg: 15,
averageHoneyKgPerColony: 7.5,
},
]);
});
});
describe('varroa', () => {
it('returns measurements and treatment counts per colony', async () => {
prisma.varroaMeasurement.findMany.mockResolvedValueOnce([
{
colonyId: 'c1',
date: new Date('2025-07-01'),
miteCount: 4,
method: 'PUDERZUCKER',
},
]);
prisma.treatment.groupBy.mockResolvedValueOnce([
{ colonyId: 'c1', _count: { _all: 2 } },
]);
const result = await service.varroa();
expect(result.measurements).toHaveLength(1);
expect(result.treatmentCountByColony).toEqual([
{ colonyId: 'c1', treatmentCount: 2 },
]);
});
});
describe('honey', () => {
it('sums yield per year and per apiary', async () => {
prisma.harvest.findMany.mockResolvedValueOnce([
{ apiaryId: 'a1', date: new Date('2025-06-01'), netWeight: 10 },
{ apiaryId: 'a1', date: new Date('2024-06-01'), netWeight: 6 },
{ apiaryId: 'a2', date: new Date('2025-06-01'), netWeight: 4 },
]);
prisma.honeyBatch.findMany.mockResolvedValueOnce([
{ amount: 8, createdAt: new Date() },
]);
const result = await service.honey();
expect(result.totalHarvestKg).toBe(20);
expect(result.totalHoneyBatchKg).toBe(8);
expect(result.yieldPerYear).toEqual({ '2025': 14, '2024': 6 });
expect(result.yieldPerApiary).toEqual({ a1: 16, a2: 4 });
});
});
describe('feeding', () => {
it('sums feeding amount per colony and per season', async () => {
prisma.feeding.findMany.mockResolvedValueOnce([
{ colonyId: 'c1', date: new Date('2025-09-01'), amount: 5, unit: 'l' },
{ colonyId: 'c1', date: new Date('2025-09-15'), amount: 3, unit: 'l' },
{ colonyId: 'c2', date: new Date('2024-09-01'), amount: 2, unit: 'l' },
]);
const result = await service.feeding();
expect(result.byColony).toEqual({ c1: 8, c2: 2 });
expect(result.bySeason).toEqual({ '2025': 8, '2024': 2 });
});
});
});