defarm-sdk
Version:
DeFarm SDK - On-premise blockchain data processing and tokenization engine for agriculture supply chain
415 lines (372 loc) • 11.8 kB
text/typescript
/**
* DeFarm Services Client - Reuses existing DeFarm monorepo services
*
* This client allows the SDK to call existing DeFarm services via API
* to reuse duplicate detection, verification engines, and other business logic
*/
import type { Asset } from '../types';
export interface DeFarmServicesConfig {
baseUrl: string;
apiKey: string;
timeout?: number;
retries?: number;
}
export interface DuplicateDetectionRequest {
category: 'livestock' | 'crops' | 'aquaculture' | 'forestry';
valuechain_type?: string;
file_name?: string;
asset_data: Record<string, any>;
}
export interface DuplicateDetectionResult {
is_duplicate: boolean;
confidence_score: number;
matches: Array<{
existing_asset_id: string;
confidence_score: number;
matching_fields: Array<{
field_path: string;
existing_value: any;
new_value: any;
match_type: 'exact' | 'fuzzy' | 'normalized';
similarity_score: number;
}>;
detection_method: string;
}>;
suggested_action: 'approve_new' | 'merge' | 'reject' | 'manual_review';
processing_time_ms: number;
candidates_checked: number;
}
export interface VerificationResult {
is_valid: boolean;
verification_score: number;
checks: Array<{
check_name: string;
status: 'passed' | 'failed' | 'warning';
message: string;
severity: 'low' | 'medium' | 'high' | 'critical';
}>;
compliance_status: {
eudr_compliant: boolean;
deforestation_risk: 'low' | 'medium' | 'high';
sustainability_score: number;
};
}
/**
* Client to reuse existing DeFarm business logic via API calls
*/
export class DeFarmServicesClient {
private config: DeFarmServicesConfig;
constructor(config: DeFarmServicesConfig) {
this.config = {
timeout: 30000,
retries: 3,
...config
};
}
/**
* Detect duplicates using existing DuplicateDetectionService
*/
async detectDuplicates(asset: Asset): Promise<DuplicateDetectionResult> {
const request: DuplicateDetectionRequest = {
category: asset.category,
valuechain_type: this.mapCategoryToValuechain(asset.category, asset.subcategory),
file_name: `sdk_${asset.dfid || asset.name}`,
asset_data: this.mapAssetToLegacyFormat(asset)
};
try {
const response = await this.makeRequest('/api/services/duplicate-detection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
});
return response.result || {
is_duplicate: false,
confidence_score: 0,
matches: [],
suggested_action: 'approve_new',
processing_time_ms: 0,
candidates_checked: 0
};
} catch (error) {
console.warn('Duplicate detection service unavailable, proceeding without checks:', error);
return {
is_duplicate: false,
confidence_score: 0,
matches: [],
suggested_action: 'approve_new',
processing_time_ms: 0,
candidates_checked: 0
};
}
}
/**
* Run verification engines using existing VerificationEngine
*/
async runVerificationEngines(asset: Asset): Promise<VerificationResult> {
const verificationRequest = {
asset_data: this.mapAssetToLegacyFormat(asset),
category: asset.category,
location: asset.ownership.location,
engines: [
'eudr_compliance',
'prodes_deforestation',
'data_quality',
'sustainability_metrics'
]
};
try {
const response = await this.makeRequest('/api/services/verification-engine', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(verificationRequest)
});
return response.result || {
is_valid: true,
verification_score: 0.8,
checks: [],
compliance_status: {
eudr_compliant: true,
deforestation_risk: 'low',
sustainability_score: 0.8
}
};
} catch (error) {
console.warn('Verification engines unavailable, proceeding without checks:', error);
return {
is_valid: true,
verification_score: 0.8,
checks: [],
compliance_status: {
eudr_compliant: true,
deforestation_risk: 'low',
sustainability_score: 0.8
}
};
}
}
/**
* Generate DFID using existing DFIDService
*/
async generateEnhancedDFID(asset: Asset): Promise<{
dfid: string;
components: {
prefix: string;
category_code: string;
location_code: string;
timestamp: string;
checksum: string;
};
}> {
try {
const response = await this.makeRequest('/api/services/dfid-generation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
asset_data: this.mapAssetToLegacyFormat(asset),
category: asset.category,
location: asset.ownership.location
})
});
return response.result;
} catch (error) {
console.warn('DFID service unavailable, using local generation:', error);
// Fallback to local DFID generation
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substring(2, 8);
const categoryCode = asset.category.substring(0, 2).toUpperCase();
return {
dfid: `DF${categoryCode}${timestamp}${random}`.toUpperCase(),
components: {
prefix: 'DF',
category_code: categoryCode,
location_code: asset.ownership.location?.country?.substring(0, 2) || 'XX',
timestamp,
checksum: random
}
};
}
}
/**
* Calculate data aggregation using existing services
*/
async calculateDataAggregation(assets: Asset[]): Promise<{
aggregated_data: Record<string, any>;
confidence_score: number;
sources_count: number;
conflicts: Array<{
field: string;
values: any[];
resolution: string;
}>;
}> {
try {
const response = await this.makeRequest('/api/services/data-aggregation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
assets: assets.map(asset => this.mapAssetToLegacyFormat(asset))
})
});
return response.result;
} catch (error) {
console.warn('Data aggregation service unavailable:', error);
// Return simple aggregation
return {
aggregated_data: assets[0]?.data || {},
confidence_score: 1.0,
sources_count: assets.length,
conflicts: []
};
}
}
/**
* Get enhanced asset timeline using existing TimelineService
*/
async getAssetTimeline(dfid: string): Promise<{
events: Array<{
timestamp: Date;
event_type: string;
description: string;
source: string;
metadata: Record<string, any>;
}>;
total_events: number;
}> {
try {
const response = await this.makeRequest(`/api/services/asset-timeline/${dfid}`, {
method: 'GET'
});
return response.result || { events: [], total_events: 0 };
} catch (error) {
console.warn('Asset timeline service unavailable:', error);
return { events: [], total_events: 0 };
}
}
/**
* Map SDK Asset to legacy DeFarm format for compatibility
*/
private mapAssetToLegacyFormat(asset: Asset): Record<string, any> {
if (asset.category === 'livestock') {
return {
animal: {
identifications: {
internal_id: asset.data.ear_tag || asset.data.internal_id,
ear_tag: asset.data.ear_tag,
rfid: asset.data.rfid,
sisbov: asset.data.sisbov
},
breed: asset.data.breed,
birth_date: asset.data.birth_date,
weight_kg: asset.data.weight_kg,
age_months: asset.data.age_months,
health_status: asset.data.health_status,
vaccinations: asset.data.vaccinations
},
ownership: {
owner_name: asset.ownership.ownerName,
brazil: {
property_name: asset.ownership.location?.municipality
}
},
location: asset.ownership.location
};
} else if (asset.category === 'crops') {
return {
crop: {
lot_number: asset.data.lot_number,
field_id: asset.data.field_id,
internal_id: asset.data.internal_id,
variety: asset.data.variety,
planting_date: asset.data.planting_date,
harvest_date: asset.data.harvest_date,
area_hectares: asset.data.area_hectares,
yield_kg_per_hectare: asset.data.yield_kg_per_hectare
},
farm: {
owner_name: asset.ownership.ownerName,
farm_name: asset.ownership.location?.municipality
},
location: asset.ownership.location
};
}
// Default mapping for other categories
return {
asset_data: asset.data,
ownership: asset.ownership,
location: asset.ownership.location,
category: asset.category,
subcategory: asset.subcategory
};
}
/**
* Map category/subcategory to valuechain type
*/
private mapCategoryToValuechain(category: string, subcategory: string): string {
const mapping: Record<string, Record<string, string>> = {
livestock: {
cattle: 'bovinos',
pigs: 'suinos',
poultry: 'aves',
sheep: 'ovinos'
},
crops: {
soybeans: 'soja',
corn: 'milho',
coffee: 'cafe',
cotton: 'algodao',
rice: 'arroz',
wheat: 'trigo'
}
};
return mapping[category]?.[subcategory] || `${category}_${subcategory}`;
}
/**
* Make HTTP request to DeFarm services
*/
private async makeRequest(endpoint: string, options: RequestInit = {}): Promise<any> {
let lastError: Error | null = null;
for (let attempt = 0; attempt <= (this.config.retries || 3); attempt++) {
try {
const url = `${this.config.baseUrl}${endpoint}`;
const requestOptions: RequestInit = {
...options,
headers: {
'Authorization': `Bearer ${this.config.apiKey}`,
'User-Agent': 'DeFarm-SDK/0.1.0',
...options.headers
},
signal: AbortSignal.timeout(this.config.timeout || 30000)
};
const response = await fetch(url, requestOptions);
if (!response.ok) {
if (response.status === 429 && attempt < (this.config.retries || 3)) {
const retryAfter = response.headers.get('Retry-After');
const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
await this.delay(waitTime);
continue;
}
const errorData = await response.json().catch(() => ({}));
throw new Error(
`DeFarm services request failed: ${response.status} ${response.statusText}. ${
errorData.message || errorData.error || ''
}`
);
}
return await response.json();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (error instanceof Error && error.name === 'AbortError') {
lastError = new Error(`DeFarm services timeout after ${this.config.timeout}ms`);
}
if (attempt < (this.config.retries || 3)) {
await this.delay(Math.pow(2, attempt) * 1000);
continue;
}
}
}
throw lastError || new Error('Unknown DeFarm services error');
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}