From 916aedb278d93b62d6b8e44734090c3103b22049 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:49:23 -0800 Subject: [PATCH] feat: add realistic plant placement script that keeps batches together by room/stage --- backend/prisma/seed-plants-realistic.js | 240 ++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 backend/prisma/seed-plants-realistic.js diff --git a/backend/prisma/seed-plants-realistic.js b/backend/prisma/seed-plants-realistic.js new file mode 100644 index 0000000..d46ba9b --- /dev/null +++ b/backend/prisma/seed-plants-realistic.js @@ -0,0 +1,240 @@ +/** + * REALISTIC PLANT PLACEMENT - 777 Wolfpack + * + * Places plants in a realistic layout: + * - Each batch stays in ONE area matching its stage + * - Veg batches โ†’ Veg rooms only + * - Flowering batches โ†’ Flower rooms only + * - Drying/Curing โ†’ Processing rooms only + * - Clones โ†’ Veg Room 2 (nursery area) + * + * Run AFTER seed-demo.js + * Usage: node prisma/seed-plants-realistic.js + */ + +const { PrismaClient } = require('@prisma/client'); +const prisma = new PrismaClient(); + +const DEMO_PREFIX = '[DEMO]'; + +async function main() { + console.log('๐ŸŒฟ Placing plants in realistic layout...\n'); + + // Clear existing plants first to start fresh + console.log('๐Ÿงน Clearing existing plant placements...'); + await prisma.facilityPlant.deleteMany({}); + await prisma.facilityPosition.updateMany({ + data: { status: 'EMPTY' } + }); + console.log(' โœ“ Cleared\n'); + + // Get all batches + const batches = await prisma.batch.findMany({ + where: { name: { startsWith: DEMO_PREFIX } }, + orderBy: { startDate: 'asc' } + }); + console.log(`๐Ÿ“ฆ Found ${batches.length} batches\n`); + + // Define which sections belong to which room types + const sectionMapping = { + // Veg Room 1 sections (Tables T1-T4) + VEG_PRIMARY: ['T1', 'T2', 'T3', 'T4'], + // Mother Room sections (Tables M1-M2) + MOTHER: ['M1', 'M2'], + // Flower Room A sections (Tables A1-A6) + FLOWER_A: ['A1', 'A2', 'A3', 'A4', 'A5', 'A6'], + // Flower Room B sections (Tables B1-B6) + FLOWER_B: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6'], + // Dry Room sections (Racks D1-D2) + DRY: ['D1', 'D2'], + // Cure Room sections (Shelves C1-C2) + CURE: ['C1', 'C2'], + }; + + // Assign batches to specific areas based on stage + const batchPlacements = []; + + for (const batch of batches) { + let targetSections = []; + let fillPercentage = 0.85; + + switch (batch.stage) { + case 'CLONE_IN': + case 'VEGETATIVE': + // Veg batches go to Veg Room 1 + targetSections = sectionMapping.VEG_PRIMARY; + fillPercentage = 0.9; + break; + case 'FLOWERING': + // Assign different flowering batches to different rooms + // Look for batches by strain to keep them separated + if (batch.strain === 'Gorilla Glue #4') { + targetSections = sectionMapping.FLOWER_A; + } else if (batch.strain === 'Wedding Cake') { + targetSections = sectionMapping.FLOWER_B; + } else { + // Zkittlez and others - put in Flower A remaining space + targetSections = ['A4', 'A5', 'A6']; + } + fillPercentage = 0.95; + break; + case 'DRYING': + targetSections = sectionMapping.DRY; + fillPercentage = 0.6; + break; + case 'CURING': + targetSections = sectionMapping.CURE; + fillPercentage = 0.5; + break; + default: + console.log(` โš ๏ธ Unknown stage for ${batch.name}: ${batch.stage}`); + continue; + } + + batchPlacements.push({ + batch, + targetSections, + fillPercentage + }); + } + + // Now place plants for each batch + for (const placement of batchPlacements) { + const { batch, targetSections, fillPercentage } = placement; + + console.log(`\n๐ŸŒฑ Placing ${batch.name}...`); + console.log(` Stage: ${batch.stage} | Strain: ${batch.strain}`); + console.log(` Target sections: ${targetSections.join(', ')}`); + + // Get all positions in these sections + const sections = await prisma.facilitySection.findMany({ + where: { code: { in: targetSections } }, + include: { + positions: true, + room: true + } + }); + + if (sections.length === 0) { + console.log(` โš ๏ธ No sections found with codes: ${targetSections.join(', ')}`); + continue; + } + + // Collect all available positions + let allPositions = []; + for (const section of sections) { + const availablePositions = section.positions.filter(p => p.status === 'EMPTY'); + allPositions = allPositions.concat( + availablePositions.map(p => ({ + ...p, + sectionCode: section.code, + roomName: section.room?.name + })) + ); + } + + // Calculate how many to fill + const targetCount = Math.min( + Math.floor(allPositions.length * fillPercentage), + batch.plantCount || allPositions.length + ); + + console.log(` Available positions: ${allPositions.length}`); + console.log(` Planting: ${targetCount} plants (${Math.round(fillPercentage * 100)}% fill)`); + + // Place plants + let planted = 0; + const plantsToCreate = []; + const positionUpdates = []; + + for (let i = 0; i < targetCount && i < allPositions.length; i++) { + const pos = allPositions[i]; + + // Generate a realistic METRC-style tag + const tagPrefix = batch.strain.substring(0, 2).toUpperCase(); + const tagNumber = `1A4-${tagPrefix}-${batch.id.substring(0, 4).toUpperCase()}-${String(i + 1).padStart(4, '0')}`; + + plantsToCreate.push({ + tagNumber, + batchId: batch.id, + positionId: pos.id, + address: `${pos.sectionCode}-R${pos.row}-C${pos.column}`, + status: 'ACTIVE' + }); + + positionUpdates.push(pos.id); + planted++; + } + + // Batch insert plants + if (plantsToCreate.length > 0) { + await prisma.facilityPlant.createMany({ + data: plantsToCreate, + skipDuplicates: true + }); + + // Update positions + await prisma.facilityPosition.updateMany({ + where: { id: { in: positionUpdates } }, + data: { status: 'OCCUPIED' } + }); + } + + console.log(` โœ… Planted ${planted} plants`); + } + + // Summary + const totalPlants = await prisma.facilityPlant.count(); + const totalOccupied = await prisma.facilityPosition.count({ where: { status: 'OCCUPIED' } }); + const totalEmpty = await prisma.facilityPosition.count({ where: { status: 'EMPTY' } }); + + console.log('\n' + '='.repeat(50)); + console.log('๐Ÿ“Š SUMMARY'); + console.log('='.repeat(50)); + console.log(`Total plants placed: ${totalPlants}`); + console.log(`Occupied positions: ${totalOccupied}`); + console.log(`Empty positions: ${totalEmpty}`); + console.log('='.repeat(50)); + + // Show per-room breakdown + console.log('\n๐Ÿ“ Plants by Room:'); + const plantsByRoom = await prisma.facilityPlant.groupBy({ + by: ['positionId'], + _count: true + }); + + const roomsWithPlants = await prisma.facilityRoom.findMany({ + include: { + sections: { + include: { + positions: { + include: { plant: true } + } + } + } + } + }); + + for (const room of roomsWithPlants) { + let roomPlantCount = 0; + for (const section of room.sections) { + for (const pos of section.positions) { + if (pos.plant) roomPlantCount++; + } + } + if (roomPlantCount > 0) { + console.log(` ${room.name}: ${roomPlantCount} plants`); + } + } + + console.log('\nโœจ Realistic plant placement complete!'); +} + +main() + .catch((e) => { + console.error('Plant placement failed:', e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + });