defarm-sdk
Version:
DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain
236 lines (196 loc) • 7.66 kB
text/typescript
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;
}
}