feat: add realistic plant placement script that keeps batches together by room/stage
This commit is contained in:
parent
76c96728f3
commit
916aedb278
1 changed files with 240 additions and 0 deletions
240
backend/prisma/seed-plants-realistic.js
Normal file
240
backend/prisma/seed-plants-realistic.js
Normal file
|
|
@ -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();
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue