/** * Vertical Farm Database Service * CRUD operations for vertical farms, zones, and crop batches */ import prisma from './prisma'; import type { VerticalFarm, GrowingZone, CropBatch, GrowingRecipe, FarmStatus, ZoneStatus, CropBatchStatus, Prisma, } from '@prisma/client'; import type { PaginationOptions, PaginatedResult } from './types'; import { createPaginatedResult } from './types'; // ============================================ // VERTICAL FARM OPERATIONS // ============================================ // Create a vertical farm export async function createVerticalFarm(data: { name: string; ownerId: string; latitude: number; longitude: number; address: string; city: string; country: string; timezone?: string; specs: Record; environmentalControl?: Record; irrigationSystem?: Record; lightingSystem?: Record; nutrientSystem?: Record; automationLevel?: 'MANUAL' | 'SEMI_AUTOMATED' | 'FULLY_AUTOMATED'; automationSystems?: Record; }): Promise { return prisma.verticalFarm.create({ data: { ...data, timezone: data.timezone || 'UTC', automationLevel: data.automationLevel || 'MANUAL', status: 'OFFLINE', }, }); } // Get vertical farm by ID export async function getVerticalFarmById(id: string): Promise { return prisma.verticalFarm.findUnique({ where: { id }, }); } // Get vertical farm with zones export async function getVerticalFarmWithZones(id: string) { return prisma.verticalFarm.findUnique({ where: { id }, include: { owner: true, zones: true, cropBatches: { where: { status: { not: 'COMPLETED' } }, }, }, }); } // Update vertical farm export async function updateVerticalFarm( id: string, data: Prisma.VerticalFarmUpdateInput ): Promise { return prisma.verticalFarm.update({ where: { id }, data, }); } // Update farm status export async function updateFarmStatus( id: string, status: FarmStatus ): Promise { return prisma.verticalFarm.update({ where: { id }, data: { status }, }); } // Delete vertical farm export async function deleteVerticalFarm(id: string): Promise { return prisma.verticalFarm.delete({ where: { id }, }); } // Get farms with pagination export async function getVerticalFarms( options: PaginationOptions = {}, filters?: { ownerId?: string; status?: FarmStatus; city?: string; country?: string; } ): Promise> { const page = options.page || 1; const limit = options.limit || 20; const skip = (page - 1) * limit; const where: Prisma.VerticalFarmWhereInput = {}; if (filters?.ownerId) where.ownerId = filters.ownerId; if (filters?.status) where.status = filters.status; if (filters?.city) where.city = { contains: filters.city, mode: 'insensitive' }; if (filters?.country) where.country = { contains: filters.country, mode: 'insensitive' }; const [farms, total] = await Promise.all([ prisma.verticalFarm.findMany({ where, skip, take: limit, orderBy: { createdAt: 'desc' }, include: { owner: true, zones: true, }, }), prisma.verticalFarm.count({ where }), ]); return createPaginatedResult(farms, total, page, limit); } // Get farms by owner export async function getVerticalFarmsByOwner(ownerId: string): Promise { return prisma.verticalFarm.findMany({ where: { ownerId }, include: { zones: true }, orderBy: { name: 'asc' }, }); } // ============================================ // GROWING ZONE OPERATIONS // ============================================ // Create a growing zone export async function createGrowingZone(data: { name: string; farmId: string; level: number; areaSqm: number; lengthM?: number; widthM?: number; growingMethod: 'NFT' | 'DWC' | 'EBB_FLOW' | 'AEROPONICS' | 'VERTICAL_TOWERS' | 'RACK_SYSTEM'; plantPositions: number; environmentTargets?: Record; }): Promise { return prisma.growingZone.create({ data: { ...data, status: 'EMPTY', }, }); } // Get growing zone by ID export async function getGrowingZoneById(id: string): Promise { return prisma.growingZone.findUnique({ where: { id }, }); } // Get growing zone with current batch export async function getGrowingZoneWithBatch(id: string) { return prisma.growingZone.findUnique({ where: { id }, include: { farm: true, cropBatches: { where: { status: { not: 'COMPLETED' } }, take: 1, }, }, }); } // Update growing zone export async function updateGrowingZone( id: string, data: Prisma.GrowingZoneUpdateInput ): Promise { return prisma.growingZone.update({ where: { id }, data, }); } // Update zone status export async function updateZoneStatus( id: string, status: ZoneStatus ): Promise { return prisma.growingZone.update({ where: { id }, data: { status }, }); } // Delete growing zone export async function deleteGrowingZone(id: string): Promise { return prisma.growingZone.delete({ where: { id }, }); } // Get zones by farm export async function getGrowingZonesByFarm(farmId: string): Promise { return prisma.growingZone.findMany({ where: { farmId }, orderBy: [{ level: 'asc' }, { name: 'asc' }], }); } // Update zone environment readings export async function updateZoneEnvironment( id: string, readings: Record ): Promise { return prisma.growingZone.update({ where: { id }, data: { currentEnvironment: readings }, }); } // ============================================ // CROP BATCH OPERATIONS // ============================================ // Create a crop batch export async function createCropBatch(data: { farmId: string; zoneId: string; cropType: string; variety?: string; recipeId?: string; seedBatchId?: string; plantCount: number; plantingDate: Date; expectedHarvestDate: Date; expectedYieldKg: number; }): Promise { // Update zone status await prisma.growingZone.update({ where: { id: data.zoneId }, data: { status: 'PLANTED', currentCrop: data.cropType, plantingDate: data.plantingDate, expectedHarvestDate: data.expectedHarvestDate, }, }); return prisma.cropBatch.create({ data: { ...data, currentStage: 'germinating', currentDay: 0, status: 'GERMINATING', }, }); } // Get crop batch by ID export async function getCropBatchById(id: string): Promise { return prisma.cropBatch.findUnique({ where: { id }, }); } // Get crop batch with details export async function getCropBatchWithDetails(id: string) { return prisma.cropBatch.findUnique({ where: { id }, include: { farm: true, zone: true, recipe: true, seedBatch: true, plants: true, harvestBatches: true, }, }); } // Update crop batch export async function updateCropBatch( id: string, data: Prisma.CropBatchUpdateInput ): Promise { return prisma.cropBatch.update({ where: { id }, data, }); } // Update crop batch status export async function updateCropBatchStatus( id: string, status: CropBatchStatus, extras?: { currentStage?: string; currentDay?: number; healthScore?: number; actualHarvestDate?: Date; actualYieldKg?: number; qualityGrade?: string; } ): Promise { const batch = await prisma.cropBatch.update({ where: { id }, data: { status, ...extras }, }); // Update zone status based on batch status if (status === 'COMPLETED' || status === 'FAILED') { await prisma.growingZone.update({ where: { id: batch.zoneId }, data: { status: 'CLEANING', currentCrop: null, plantingDate: null, expectedHarvestDate: null, }, }); } else if (status === 'HARVESTING') { await prisma.growingZone.update({ where: { id: batch.zoneId }, data: { status: 'HARVESTING' }, }); } else if (status === 'GROWING') { await prisma.growingZone.update({ where: { id: batch.zoneId }, data: { status: 'GROWING' }, }); } return batch; } // Delete crop batch export async function deleteCropBatch(id: string): Promise { return prisma.cropBatch.delete({ where: { id }, }); } // Get crop batches with pagination export async function getCropBatches( options: PaginationOptions = {}, filters?: { farmId?: string; zoneId?: string; status?: CropBatchStatus; cropType?: string; } ): Promise> { const page = options.page || 1; const limit = options.limit || 20; const skip = (page - 1) * limit; const where: Prisma.CropBatchWhereInput = {}; if (filters?.farmId) where.farmId = filters.farmId; if (filters?.zoneId) where.zoneId = filters.zoneId; if (filters?.status) where.status = filters.status; if (filters?.cropType) where.cropType = { contains: filters.cropType, mode: 'insensitive' }; const [batches, total] = await Promise.all([ prisma.cropBatch.findMany({ where, skip, take: limit, orderBy: { plantingDate: 'desc' }, include: { zone: true, recipe: true, }, }), prisma.cropBatch.count({ where }), ]); return createPaginatedResult(batches, total, page, limit); } // Get active batches by farm export async function getActiveCropBatchesByFarm(farmId: string): Promise { return prisma.cropBatch.findMany({ where: { farmId, status: { notIn: ['COMPLETED', 'FAILED'] }, }, include: { zone: true, recipe: true, }, orderBy: { expectedHarvestDate: 'asc' }, }); } // Add issue to crop batch export async function addCropBatchIssue( id: string, issue: { type: string; severity: string; description: string; affectedPlants: number; } ) { const batch = await prisma.cropBatch.findUnique({ where: { id }, select: { issues: true }, }); const issues = (batch?.issues as unknown[] || []) as Record[]; issues.push({ id: `issue_${Date.now()}`, timestamp: new Date().toISOString(), ...issue, }); return prisma.cropBatch.update({ where: { id }, data: { issues }, }); } // ============================================ // GROWING RECIPE OPERATIONS // ============================================ // Create a growing recipe export async function createGrowingRecipe(data: { name: string; cropType: string; variety?: string; version?: string; stages: Record[]; expectedDays: number; expectedYieldGrams: number; expectedYieldPerSqm?: number; requirements?: Record; source?: 'INTERNAL' | 'COMMUNITY' | 'COMMERCIAL'; author?: string; }): Promise { return prisma.growingRecipe.create({ data: { ...data, version: data.version || '1.0', source: data.source || 'INTERNAL', timesUsed: 0, }, }); } // Get growing recipe by ID export async function getGrowingRecipeById(id: string): Promise { return prisma.growingRecipe.findUnique({ where: { id }, }); } // Get recipes by crop type export async function getGrowingRecipesByCrop(cropType: string): Promise { return prisma.growingRecipe.findMany({ where: { cropType: { contains: cropType, mode: 'insensitive' } }, orderBy: { rating: 'desc' }, }); } // Increment recipe usage export async function incrementRecipeUsage(id: string): Promise { return prisma.growingRecipe.update({ where: { id }, data: { timesUsed: { increment: 1 } }, }); } // ============================================ // RESOURCE & ANALYTICS OPERATIONS // ============================================ // Record resource usage export async function recordResourceUsage(data: { farmId: string; periodStart: Date; periodEnd: Date; electricityKwh: number; electricityCostUsd?: number; renewablePercent?: number; peakDemandKw?: number; waterUsageL: number; waterCostUsd?: number; waterRecycledPercent?: number; co2UsedKg?: number; co2CostUsd?: number; nutrientsUsedL?: number; nutrientCostUsd?: number; }) { return prisma.resourceUsage.create({ data }); } // Get resource usage for farm export async function getResourceUsage(farmId: string, periodStart: Date, periodEnd: Date) { return prisma.resourceUsage.findMany({ where: { farmId, periodStart: { gte: periodStart }, periodEnd: { lte: periodEnd }, }, orderBy: { periodStart: 'asc' }, }); } // Record farm analytics export async function recordFarmAnalytics(data: { farmId: string; period: string; totalYieldKg: number; yieldPerSqmPerYear?: number; cropCyclesCompleted: number; averageCyclesDays?: number; averageQualityScore?: number; gradeAPercent?: number; wastagePercent?: number; cropSuccessRate?: number; spaceUtilization?: number; laborHoursPerKg?: number; revenueUsd?: number; costUsd?: number; profitMarginPercent?: number; revenuePerSqm?: number; carbonFootprintKgPerKg?: number; waterUseLPerKg?: number; energyUseKwhPerKg?: number; topCropsByYield?: Record[]; topCropsByRevenue?: Record[]; topCropsByEfficiency?: Record[]; }) { return prisma.farmAnalytics.create({ data }); } // Get farm analytics export async function getFarmAnalytics(farmId: string, period?: string) { const where: Prisma.FarmAnalyticsWhereInput = { farmId }; if (period) where.period = period; return prisma.farmAnalytics.findMany({ where, orderBy: { generatedAt: 'desc' }, take: period ? 1 : 12, }); }