UNPKG

defarm-sdk

Version:

DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain

236 lines (196 loc) 7.66 kB
import { BaseProcessor } from './BaseProcessor'; import type { Asset, ValidationError } from '../types'; /** * Processor for crops assets (soybeans, corn, wheat, coffee, etc.) */ export class CropsProcessor extends BaseProcessor { name = 'CropsProcessor'; version = '1.0.0'; protected async validateCategory(asset: Asset): Promise<ValidationError[]> { const errors: ValidationError[] = []; // Validate subcategory const validSubcategories = ['soybeans', 'corn', 'wheat', 'coffee', 'sugarcane', 'cotton', 'rice', 'other']; if (!validSubcategories.includes(asset.subcategory)) { errors.push({ field: 'subcategory', message: `Invalid crop subcategory. Must be one of: ${validSubcategories.join(', ')}`, code: 'INVALID_SUBCATEGORY' }); } // Validate area const areaError = this.validateNumericField(asset, 'area_hectares', 0.01, 100000); if (areaError) errors.push(areaError); // Validate yield if present const yieldError = this.validateNumericField(asset, 'estimated_yield_kg', 0, 1000000); if (yieldError) errors.push(yieldError); // Validate planting date const plantingDateError = this.validateDateField(asset, 'planting_date', true); if (plantingDateError) errors.push(plantingDateError); // Validate harvest date if present const harvestDateError = this.validateDateField(asset, 'harvest_date', true); if (harvestDateError) errors.push(harvestDateError); // Ensure harvest date is after planting date if (asset.data.planting_date && asset.data.harvest_date) { const plantingDate = new Date(asset.data.planting_date); const harvestDate = new Date(asset.data.harvest_date); if (harvestDate <= plantingDate) { errors.push({ field: 'data.harvest_date', message: 'Harvest date must be after planting date', code: 'INVALID_DATE_SEQUENCE' }); } } return errors; } protected async processCategory(asset: Asset): Promise<Asset> { // Ensure standard crop fields exist this.ensureDataField(asset, 'cultivation_type', 'conventional'); this.ensureDataField(asset, 'irrigation_type', 'rainfed'); this.ensureDataField(asset, 'soil_type', 'unknown'); // Calculate growing season if dates are available if (asset.data.planting_date && asset.data.harvest_date) { const plantingDate = new Date(asset.data.planting_date); const harvestDate = new Date(asset.data.harvest_date); const growingDays = Math.floor( (harvestDate.getTime() - plantingDate.getTime()) / (1000 * 60 * 60 * 24) ); asset.data.growing_season_days = growingDays; } // Calculate productivity if yield and area are available if (asset.data.estimated_yield_kg && asset.data.area_hectares) { asset.data.productivity_kg_per_hectare = Math.round(asset.data.estimated_yield_kg / asset.data.area_hectares); } // Process based on subcategory switch (asset.subcategory) { case 'soybeans': asset = this.processSoybeans(asset); break; case 'corn': asset = this.processCorn(asset); break; case 'coffee': asset = this.processCoffee(asset); break; } // Add crop-specific metadata asset.data.season = this.determineSeason(asset.data.planting_date); asset.data.last_processed = new Date().toISOString(); return asset; } private processSoybeans(asset: Asset): Asset { // Soybean-specific processing if (!asset.data.variety) { // Set default variety based on region (simplified) asset.data.variety = 'conventional'; } // Validate expected productivity ranges if (asset.data.productivity_kg_per_hectare) { const productivity = asset.data.productivity_kg_per_hectare; if (productivity < 1000) { asset.data.productivity_assessment = 'low'; } else if (productivity < 3000) { asset.data.productivity_assessment = 'average'; } else if (productivity < 4000) { asset.data.productivity_assessment = 'good'; } else { asset.data.productivity_assessment = 'excellent'; } } return asset; } private processCorn(asset: Asset): Asset { // Corn-specific processing if (!asset.data.purpose) { asset.data.purpose = 'grain'; // vs 'silage' } // Different productivity ranges for corn if (asset.data.productivity_kg_per_hectare) { const productivity = asset.data.productivity_kg_per_hectare; if (productivity < 3000) { asset.data.productivity_assessment = 'low'; } else if (productivity < 6000) { asset.data.productivity_assessment = 'average'; } else if (productivity < 9000) { asset.data.productivity_assessment = 'good'; } else { asset.data.productivity_assessment = 'excellent'; } } return asset; } private processCoffee(asset: Asset): Asset { // Coffee-specific processing if (!asset.data.processing_method) { asset.data.processing_method = 'natural'; // vs 'washed', 'honey' } if (!asset.data.altitude_meters && asset.ownership.location?.coordinates) { // In real implementation, would look up altitude from coordinates asset.data.altitude_meters = 1200; // Default estimate } // Coffee quality indicators if (asset.data.altitude_meters) { if (asset.data.altitude_meters > 1500) { asset.data.altitude_quality = 'specialty'; } else if (asset.data.altitude_meters > 1000) { asset.data.altitude_quality = 'premium'; } else { asset.data.altitude_quality = 'standard'; } } return asset; } private determineSeason(plantingDate?: string): string { if (!plantingDate) return 'unknown'; const date = new Date(plantingDate); const month = date.getMonth(); // 0-11 // Simplified for Brazil if (month >= 9 || month <= 1) { return 'main-season'; // Oct-Feb } else if (month >= 2 && month <= 4) { return 'off-season'; // Mar-May (safrinha) } else { return 'winter-crop'; // Jun-Sep } } // Optional: Override hooks for crop-specific needs async beforeProcess(asset: Asset): Promise<Asset> { // Normalize variety names if (asset.data.variety) { asset.data.variety = asset.data.variety.toLowerCase().trim(); } // Convert common unit variations if (asset.data.yield_bags && !asset.data.estimated_yield_kg) { // Convert bags to kg (assuming 60kg bags for grains) const kgPerBag = asset.subcategory === 'coffee' ? 60 : 60; asset.data.estimated_yield_kg = asset.data.yield_bags * kgPerBag; } return asset; } async afterProcess(asset: Asset): Promise<Asset> { // Calculate sustainability score let sustainabilityScore = 50; // Base score // Positive factors if (asset.data.cultivation_type === 'organic') { sustainabilityScore += 30; } else if (asset.data.cultivation_type === 'sustainable') { sustainabilityScore += 20; } if (asset.data.irrigation_type === 'drip' || asset.data.irrigation_type === 'micro-sprinkler') { sustainabilityScore += 15; } if (asset.data.cover_crop === true) { sustainabilityScore += 10; } if (asset.data.crop_rotation === true) { sustainabilityScore += 10; } // Negative factors if (asset.data.pesticide_use === 'high') { sustainabilityScore -= 20; } asset.data.sustainability_score = Math.min(100, Math.max(0, sustainabilityScore)); return asset; } }