/** * Demand to Harvest Integration Tests * Tests the complete flow from demand signal to plant to harvest */ import { TransportChain, setTransportChain } from '../../lib/transport/tracker'; import { DemandForecaster } from '../../lib/demand/forecaster'; import { ConsumerPreference, PlantingRecommendation, } from '../../lib/demand/types'; import { SeedAcquisitionEvent, PlantingEvent, HarvestEvent, DistributionEvent, ConsumerDeliveryEvent, TransportLocation, } from '../../lib/transport/types'; describe('Demand to Harvest Integration', () => { let chain: TransportChain; let forecaster: DemandForecaster; beforeEach(() => { chain = new TransportChain(1); setTransportChain(chain); forecaster = new DemandForecaster(); }); describe('Complete Demand-Driven Flow', () => { it('should complete full flow from demand to consumer delivery', () => { // Step 1: Register consumer demand const consumerId = 'consumer-integration-001'; const pref: ConsumerPreference = { consumerId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), location: { latitude: 40.7128, longitude: -74.006, maxDeliveryRadiusKm: 25, city: 'New York', }, dietaryType: ['omnivore'], allergies: [], dislikes: [], preferredCategories: ['leafy_greens'], preferredItems: [ { produceType: 'lettuce', category: 'leafy_greens', priority: 'must_have', weeklyQuantity: 5, seasonalOnly: false }, ], certificationPreferences: ['organic', 'local'], freshnessImportance: 5, priceImportance: 3, sustainabilityImportance: 5, deliveryPreferences: { method: ['home_delivery'], frequency: 'weekly', preferredDays: ['saturday'], }, householdSize: 4, weeklyBudget: 100, currency: 'USD', }; forecaster.registerPreference(pref); // Step 2: Generate demand signal const signal = forecaster.generateDemandSignal( 40.7128, -74.006, 50, 'NYC Metro', 'spring' ); expect(signal.totalConsumers).toBe(1); expect(signal.demandItems.length).toBeGreaterThan(0); const lettuceItem = signal.demandItems.find(i => i.produceType === 'lettuce'); expect(lettuceItem).toBeDefined(); expect(lettuceItem!.weeklyDemandKg).toBe(20); // 5 kg * 4 household size // Step 3: Generate planting recommendation for grower const recommendations = forecaster.generatePlantingRecommendations( 'grower-integration-001', 40.72, -74.01, // Near NYC 50, // 50km delivery radius 100, // 100 sqm available 'spring' ); expect(recommendations.length).toBeGreaterThan(0); const lettuceRec = recommendations.find(r => r.produceType === 'lettuce'); expect(lettuceRec).toBeDefined(); // Step 4: Grower follows recommendation and plants const seedBatchId = 'demand-driven-batch-001'; const plantIds = ['demand-plant-001', 'demand-plant-002']; const seedEvent: SeedAcquisitionEvent = { id: 'demand-seed-001', timestamp: new Date().toISOString(), eventType: 'seed_acquisition', fromLocation: createLocation('seed_bank', 'Local Organic Seeds'), toLocation: createLocation('greenhouse', 'Grower Greenhouse'), distanceKm: 10, durationMinutes: 20, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'seed-supplier', receiverId: 'grower-integration-001', status: 'verified', seedBatchId, sourceType: 'purchase', species: 'Lactuca sativa', variety: 'Butterhead', quantity: 100, quantityUnit: 'seeds', generation: 1, certifications: ['organic'], }; chain.recordEvent(seedEvent); const plantingEvent: PlantingEvent = { id: 'demand-planting-001', timestamp: new Date(Date.now() + 1000).toISOString(), eventType: 'planting', fromLocation: createLocation('greenhouse', 'Grower Greenhouse'), toLocation: createLocation('greenhouse', 'Grower Greenhouse'), distanceKm: 0, durationMinutes: 30, transportMethod: 'walking', carbonFootprintKg: 0, senderId: 'grower-integration-001', receiverId: 'grower-integration-001', status: 'verified', seedBatchId, plantIds, plantingMethod: 'indoor_start', quantityPlanted: plantIds.length, growingEnvironment: 'greenhouse', expectedHarvestDate: new Date(Date.now() + 45 * 24 * 60 * 60 * 1000).toISOString(), }; chain.recordEvent(plantingEvent); // Step 5: Harvest const harvestBatchId = 'demand-harvest-batch-001'; const harvestEvent: HarvestEvent = { id: 'demand-harvest-001', timestamp: new Date(Date.now() + 45 * 24 * 60 * 60 * 1000).toISOString(), eventType: 'harvest', fromLocation: createLocation('greenhouse', 'Grower Greenhouse'), toLocation: createLocation('hub', 'Distribution Hub'), distanceKm: 5, durationMinutes: 15, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'grower-integration-001', receiverId: 'hub-distribution', status: 'verified', plantIds, harvestBatchId, harvestType: 'full', produceType: 'Butterhead Lettuce', grossWeight: 2, netWeight: 1.8, weightUnit: 'kg', qualityGrade: 'A', packagingType: 'sustainable_boxes', temperatureRequired: { min: 2, max: 8, optimal: 4, unit: 'celsius' }, shelfLifeHours: 168, seedsSaved: false, }; chain.recordEvent(harvestEvent); // Step 6: Distribution const distributionEvent: DistributionEvent = { id: 'demand-distribution-001', timestamp: new Date(Date.now() + 46 * 24 * 60 * 60 * 1000).toISOString(), eventType: 'distribution', fromLocation: createLocation('hub', 'Distribution Hub'), toLocation: createLocation('market', 'Local Delivery Zone'), distanceKm: 10, durationMinutes: 20, transportMethod: 'electric_truck', carbonFootprintKg: 0, senderId: 'hub-distribution', receiverId: 'delivery-service', status: 'verified', batchIds: [harvestBatchId], destinationType: 'consumer', customerType: 'individual', deliveryWindow: { start: new Date(Date.now() + 46 * 24 * 60 * 60 * 1000).toISOString(), end: new Date(Date.now() + 46.5 * 24 * 60 * 60 * 1000).toISOString(), }, deliveryAttempts: 1, handoffVerified: true, }; chain.recordEvent(distributionEvent); // Step 7: Consumer Delivery const deliveryEvent: ConsumerDeliveryEvent = { id: 'demand-delivery-001', timestamp: new Date(Date.now() + 46.25 * 24 * 60 * 60 * 1000).toISOString(), eventType: 'consumer_delivery', fromLocation: createLocation('market', 'Local Delivery Zone'), toLocation: createLocation('consumer', 'Consumer Home'), distanceKm: 3, durationMinutes: 10, transportMethod: 'electric_vehicle', carbonFootprintKg: 0, senderId: 'delivery-service', receiverId: consumerId, status: 'verified', orderId: 'order-integration-001', batchIds: [harvestBatchId], deliveryMethod: 'home_delivery', finalMileMethod: 'electric_vehicle', packagingReturned: true, feedbackReceived: true, feedbackRating: 5, feedbackNotes: 'Fresh and delicious!', }; chain.recordEvent(deliveryEvent); // Verify complete chain expect(chain.isChainValid()).toBe(true); expect(chain.chain.length).toBe(7); // Genesis + 6 events // Verify plant journey const journey = chain.getPlantJourney(plantIds[0]); expect(journey).not.toBeNull(); expect(journey!.events.length).toBe(3); // planting, harvest, and possibly more // Verify environmental impact const impact = chain.getEnvironmentalImpact('grower-integration-001'); expect(impact.totalFoodMiles).toBeLessThan(50); // Local delivery expect(impact.comparisonToConventional.percentageReduction).toBeGreaterThan(0); }); }); describe('Supply Registration Affects Recommendations', () => { it('should adjust recommendations based on existing supply', () => { // Register high demand const pref = createConsumerPreference('consumer-001'); pref.preferredItems = [ { produceType: 'lettuce', category: 'leafy_greens', priority: 'must_have', weeklyQuantity: 100, seasonalOnly: false }, ]; pref.householdSize = 1; forecaster.registerPreference(pref); forecaster.generateDemandSignal(40.7, -74.0, 50, 'Test', 'spring'); // Get recommendations without supply const recsWithoutSupply = forecaster.generatePlantingRecommendations( 'grower-001', 40.7, -74.0, 50, 100, 'spring' ); // Register significant supply forecaster.registerSupply({ id: 'supply-001', growerId: 'other-grower', timestamp: new Date().toISOString(), produceType: 'lettuce', committedQuantityKg: 80, availableFrom: new Date().toISOString(), availableUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), pricePerKg: 5, currency: 'USD', minimumOrderKg: 1, certifications: ['organic'], freshnessGuaranteeHours: 48, deliveryRadiusKm: 50, deliveryMethods: ['grower_delivery'], status: 'available', remainingKg: 80, }); // Generate new demand signal (with supply factored in) forecaster.generateDemandSignal(40.7, -74.0, 50, 'Test', 'spring'); // Get recommendations with supply const recsWithSupply = forecaster.generatePlantingRecommendations( 'grower-001', 40.7, -74.0, 50, 100, 'spring' ); // Recommendations should be different (less space recommended for lettuce) if (recsWithoutSupply.length > 0 && recsWithSupply.length > 0) { const lettuceWithout = recsWithoutSupply.find(r => r.produceType === 'lettuce'); const lettuceWith = recsWithSupply.find(r => r.produceType === 'lettuce'); if (lettuceWithout && lettuceWith) { expect(lettuceWith.recommendedQuantity).toBeLessThanOrEqual(lettuceWithout.recommendedQuantity); } } }); }); describe('Regional Demand Matching', () => { it('should match growers with regional demand', () => { // Create consumers in different regions const nycConsumer = createConsumerPreference('nyc-consumer'); nycConsumer.location = { latitude: 40.7128, longitude: -74.006, maxDeliveryRadiusKm: 10 }; forecaster.registerPreference(nycConsumer); const laConsumer = createConsumerPreference('la-consumer'); laConsumer.location = { latitude: 34.0522, longitude: -118.2437, maxDeliveryRadiusKm: 10 }; forecaster.registerPreference(laConsumer); // Generate signals for each region const nycSignal = forecaster.generateDemandSignal(40.7128, -74.006, 20, 'NYC', 'spring'); const laSignal = forecaster.generateDemandSignal(34.0522, -118.2437, 20, 'LA', 'spring'); expect(nycSignal.totalConsumers).toBe(1); expect(laSignal.totalConsumers).toBe(1); // NYC grower should only see NYC demand const nycGrowerRecs = forecaster.generatePlantingRecommendations( 'nyc-grower', 40.72, -74.01, 25, 100, 'spring' ); // LA grower should only see LA demand const laGrowerRecs = forecaster.generatePlantingRecommendations( 'la-grower', 34.06, -118.25, 25, 100, 'spring' ); // Both should have recommendations from their respective regions expect(nycGrowerRecs.length + laGrowerRecs.length).toBeGreaterThanOrEqual(0); }); }); describe('Seasonal Demand Flow', () => { it('should respect seasonal availability in recommendations', () => { const pref = createConsumerPreference('seasonal-consumer'); pref.preferredItems = [ { produceType: 'tomato', category: 'nightshades', priority: 'must_have', weeklyQuantity: 5, seasonalOnly: false }, { produceType: 'lettuce', category: 'leafy_greens', priority: 'must_have', weeklyQuantity: 5, seasonalOnly: false }, ]; forecaster.registerPreference(pref); // Winter recommendations forecaster.generateDemandSignal(40.7, -74.0, 50, 'Test', 'winter'); const winterRecs = forecaster.generatePlantingRecommendations( 'grower-001', 40.7, -74.0, 50, 100, 'winter' ); // Tomato should not be recommended in winter (not in season) const tomatoWinter = winterRecs.find(r => r.produceType === 'tomato'); expect(tomatoWinter).toBeUndefined(); // Summer recommendations forecaster.generateDemandSignal(40.7, -74.0, 50, 'Test', 'summer'); const summerRecs = forecaster.generatePlantingRecommendations( 'grower-001', 40.7, -74.0, 50, 100, 'summer' ); // Tomato should be recommended in summer const tomatoSummer = summerRecs.find(r => r.produceType === 'tomato'); expect(tomatoSummer).toBeDefined(); }); }); }); // Helper functions function createLocation(type: string, name: string): TransportLocation { return { latitude: 40.7 + Math.random() * 0.1, longitude: -74.0 + Math.random() * 0.1, locationType: type as any, facilityName: name, }; } function createConsumerPreference(consumerId: string): ConsumerPreference { return { consumerId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), location: { latitude: 40.7128, longitude: -74.006, maxDeliveryRadiusKm: 25, }, dietaryType: ['omnivore'], allergies: [], dislikes: [], preferredCategories: ['leafy_greens'], preferredItems: [ { produceType: 'lettuce', category: 'leafy_greens', priority: 'must_have', weeklyQuantity: 2, seasonalOnly: false }, ], certificationPreferences: ['organic'], freshnessImportance: 4, priceImportance: 3, sustainabilityImportance: 4, deliveryPreferences: { method: ['home_delivery'], frequency: 'weekly', preferredDays: ['saturday'], }, householdSize: 2, weeklyBudget: 100, currency: 'USD', }; }