/**
* Import Script für Test Farm Datensätze
* Importiert 3 verschiedene Locations mit historischen Daten (2023-2024)
*/
import dotenv from "dotenv";
import { LiteFarmClient } from "./litefarm-client.js";
// Load environment variables
dotenv.config();
// Test Farm Datensätze
const TEST_FARM_DATA = {
"datasets": [
{
"context": "Historical Data 2024",
"location": {
"name": "Gewächshaus Block B",
"type": "Greenhouse",
"dimensions": "50m2"
},
"crop": {
"name": "Tomate",
"variety": "Moneymaker",
"type": "Solanaceae"
},
"harvest_logs": [
{ "date": "2024-07-15", "quantity": 45, "unit": "kg" },
{ "date": "2024-07-22", "quantity": 60, "unit": "kg" },
{ "date": "2024-08-05", "quantity": 120, "unit": "kg" },
{ "date": "2024-08-20", "quantity": 95, "unit": "kg" },
{ "date": "2024-09-10", "quantity": 50, "unit": "kg" }
],
"task_logs": [
{ "date": "2024-05-01", "task": "Planting", "duration_minutes": 240, "workers": 2, "notes": "Pflanzung von Hand" },
{ "date": "2024-06-01", "task": "Pruning", "duration_minutes": 90, "workers": 1, "notes": "Ausgeizen Woche 1" },
{ "date": "2024-06-15", "task": "Pruning", "duration_minutes": 120, "workers": 1, "notes": "Ausgeizen & Aufleiten" },
{ "date": "2024-07-01", "task": "Pruning", "duration_minutes": 120, "workers": 1, "notes": "Ausgeizen starker Wuchs" },
{ "date": "2024-07-15", "task": "Harvesting", "duration_minutes": 60, "workers": 1 },
{ "date": "2024-07-22", "task": "Harvesting", "duration_minutes": 90, "workers": 1 },
{ "date": "2024-08-05", "task": "Harvesting", "duration_minutes": 150, "workers": 2 },
{ "date": "2024-08-20", "task": "Harvesting", "duration_minutes": 120, "workers": 1 },
{ "date": "2024-09-15", "task": "Cleanup", "duration_minutes": 300, "workers": 2, "notes": "Kulturräumung" }
]
},
{
"context": "Historical Data 2024",
"location": {
"name": "Feld Nord - Beet 4",
"type": "Outdoor Bed",
"dimensions": "100m2"
},
"crop": {
"name": "Zucchini",
"variety": "Black Beauty",
"type": "Cucurbitaceae"
},
"harvest_logs": [
{ "date": "2024-06-20", "quantity": 120, "unit": "kg" },
{ "date": "2024-07-10", "quantity": 250, "unit": "kg" },
{ "date": "2024-08-01", "quantity": 180, "unit": "kg" }
],
"task_logs": [
{ "date": "2024-05-15", "task": "Planting", "duration_minutes": 120, "workers": 1, "notes": "Direktsaat" },
{ "date": "2024-06-05", "task": "Weeding", "duration_minutes": 45, "workers": 1, "notes": "Hackdurchgang maschinell" },
{ "date": "2024-06-20", "task": "Harvesting", "duration_minutes": 60, "workers": 1 },
{ "date": "2024-07-10", "task": "Harvesting", "duration_minutes": 90, "workers": 1 },
{ "date": "2024-08-01", "task": "Harvesting", "duration_minutes": 80, "workers": 1 }
]
},
{
"context": "Soil History",
"location": {
"name": "Beet 10",
"current_status": "Empty",
"soil_type": "Loam"
},
"history_2023": {
"crop": "Winterkohl",
"family": "Brassicaceae",
"harvest_date": "2023-11-30",
"disease_pressure": "High",
"notes": "Leichter Befall von Kohlhernie beobachtet"
},
"history_2024": {
"crop": "Gründüngung (Wicke)",
"family": "Fabaceae",
"terminated_date": "2024-04-01"
},
"planned_2025": null
}
]
};
interface ImportStats {
farmId?: string;
locationsCreated: number;
locationErrors: number;
tasksCreated: number;
taskErrors: number;
harvestsCreated: number;
warnings: string[];
}
// Task type mapping
const TASK_TYPE_MAP: Record<string, string> = {
"Planting": "plant_task",
"Pruning": "field_work_task",
"Harvesting": "harvest_task",
"Weeding": "field_work_task",
"Cleanup": "cleaning_task"
};
class TestFarmImporter {
private client: LiteFarmClient;
private stats: ImportStats;
private farmId: string | null = null;
private locationMapping: Map<string, string> = new Map(); // Location name -> location_id
private cropMapping: Map<string, string> = new Map(); // Crop name -> crop_id
private taskTypeMapping: Map<string, number> = new Map(); // Task type name -> task_type_id
constructor(email: string, password: string) {
this.client = new LiteFarmClient(email, password);
this.stats = {
locationsCreated: 0,
locationErrors: 0,
tasksCreated: 0,
taskErrors: 0,
harvestsCreated: 0,
warnings: []
};
}
async run(): Promise<void> {
try {
console.log("\n🚀 Starting Test Farm Data Import...\n");
// Login
console.log("1️⃣ Logging in...");
await this.client.login();
console.log("✅ Login successful\n");
// Get farm ID
console.log("2️⃣ Getting farm...");
await this.getFarmId();
console.log(`✅ Using farm ID: ${this.farmId}\n`);
// Select farm (CRITICAL: Sets farm_id header for all subsequent requests)
console.log("3️⃣ Selecting farm...");
await this.client.selectFarm(this.farmId!);
console.log(`✅ Farm selected: ${this.farmId}\n`);
// Load mappings
console.log("4️⃣ Loading mappings...");
await this.loadMappings();
console.log(`✅ Loaded ${this.cropMapping.size} crops\n`);
// Process all datasets
console.log("5️⃣ Processing datasets...\n");
for (let i = 0; i < TEST_FARM_DATA.datasets.length; i++) {
const dataset = TEST_FARM_DATA.datasets[i];
console.log(`\n📦 Dataset ${i + 1}/3: ${dataset.location.name}`);
await this.processDataset(dataset);
}
// Print summary
this.printSummary();
} catch (error) {
console.error("\n❌ Import failed:", error);
throw error;
}
}
private async getFarmId(): Promise<void> {
const farms = await this.client.getFarms();
if (farms.length === 0) {
throw new Error("No farms found. Please create a farm first.");
}
this.farmId = farms[0].farm_id;
this.stats.farmId = this.farmId;
}
private async loadMappings(): Promise<void> {
try {
// Load crops
const crops = await this.client.getCrops();
crops.forEach(crop => {
this.cropMapping.set(crop.crop_common_name.toLowerCase(), crop.crop_id);
});
// Load task types - correct endpoint is /task_type/farm/:farm_id
if (this.farmId) {
const taskTypes = await this.client.get<any[]>(`/task_type/farm/${this.farmId}`);
taskTypes.forEach(tt => {
this.taskTypeMapping.set(tt.task_translation_key?.toLowerCase(), tt.task_type_id);
});
}
} catch (error) {
console.warn("⚠️ Could not load some mappings:", error);
}
}
private async processDataset(dataset: any): Promise<void> {
try {
// Create location
const locationId = await this.createLocation(dataset.location);
if (!locationId) {
console.error(` ✗ Failed to create location: ${dataset.location.name}`);
return;
}
// Process based on dataset type
if (dataset.context === "Historical Data 2024") {
await this.processHistoricalData(dataset, locationId);
} else if (dataset.context === "Soil History") {
await this.processSoilHistory(dataset, locationId);
}
} catch (error) {
console.error(` ✗ Error processing dataset:`, error);
this.stats.warnings.push(`Dataset ${dataset.location.name}: ${error}`);
}
}
private async createLocation(locationInfo: any): Promise<string | null> {
try {
// Parse dimensions (e.g., "50m2" → 50)
const area_m2 = parseInt(locationInfo.dimensions?.replace(/[^\d]/g, '') || '0');
// Map location type to LiteFarm type
let locationType = 'field';
if (locationInfo.type === 'Greenhouse') {
locationType = 'greenhouse';
} else if (locationInfo.type === 'Outdoor Bed') {
locationType = 'garden';
}
// LiteFarm requires specific structure: { locationType: {...}, figure: { area/line/point: {...} }, ...props }
const locationData: any = {
farm_id: this.farmId,
name: locationInfo.name,
notes: locationInfo.soil_type ? `Bodentyp: ${locationInfo.soil_type}` : '',
figure: {
type: locationType,
area: {
total_area: area_m2,
total_area_unit: 'm2',
grid_points: [
{ lat: 49.0, lng: 8.4 },
{ lat: 49.001, lng: 8.4 },
{ lat: 49.001, lng: 8.401 },
{ lat: 49.0, lng: 8.401 }
]
}
}
};
// Add location-type specific data with organic_history (required by API)
const today = new Date().toISOString().split('T')[0];
if (locationType === 'field') {
locationData.field = {
organic_status: 'Non-Organic',
organic_history: {
effective_date: today,
organic_status: 'Non-Organic'
}
};
} else if (locationType === 'greenhouse') {
locationData.greenhouse = {
organic_status: 'Non-Organic',
organic_history: {
effective_date: today,
organic_status: 'Non-Organic'
}
};
} else if (locationType === 'garden') {
locationData.garden = {
organic_status: 'Non-Organic',
organic_history: {
effective_date: today,
organic_status: 'Non-Organic'
}
};
}
const location = await this.client.post<any>(`/location/${locationType}`, locationData);
this.locationMapping.set(locationInfo.name, location.location_id);
this.stats.locationsCreated++;
console.log(` ✓ Created location: ${locationInfo.name} (${area_m2}m², ${locationType})`);
return location.location_id;
} catch (error) {
this.stats.locationErrors++;
console.error(` ✗ Failed to create location:`, error);
return null;
}
}
private async processHistoricalData(dataset: any, locationId: string): Promise<void> {
// Create tasks
for (const taskLog of dataset.task_logs || []) {
await this.createTask(taskLog, locationId, dataset);
}
// Harvest tasks are already included in task_logs for this dataset
console.log(` → Processed ${dataset.task_logs?.length || 0} tasks`);
}
private async processSoilHistory(dataset: any, locationId: string): Promise<void> {
// Create notes for historical crops
const notes: string[] = [];
if (dataset.history_2023) {
notes.push(`== Geschichte 2023 ==`);
notes.push(`Kultur: ${dataset.history_2023.crop} (${dataset.history_2023.family})`);
notes.push(`Ernte: ${dataset.history_2023.harvest_date}`);
notes.push(`Krankheitsdruck: ${dataset.history_2023.disease_pressure}`);
notes.push(`Notizen: ${dataset.history_2023.notes}`);
}
if (dataset.history_2024) {
notes.push(`\n== Geschichte 2024 ==`);
notes.push(`Kultur: ${dataset.history_2024.crop} (${dataset.history_2024.family})`);
notes.push(`Beendet: ${dataset.history_2024.terminated_date}`);
}
// Update location with historical notes
try {
await this.client.patch(`/location/${dataset.location.type?.toLowerCase() || 'field'}/${locationId}`, {
notes: notes.join('\n')
});
console.log(` ✓ Added soil history notes`);
} catch (error) {
console.error(` ✗ Failed to add soil history:`, error);
}
}
private async createTask(taskLog: any, locationId: string, dataset: any): Promise<void> {
try {
const taskType = TASK_TYPE_MAP[taskLog.task];
if (!taskType) {
console.warn(` ⚠️ Unknown task type: ${taskLog.task}`);
return;
}
// Build notes with worker info
let notes = taskLog.notes || '';
if (taskLog.workers > 1) {
const totalTime = taskLog.duration_minutes * taskLog.workers;
notes += `\n${taskLog.workers} Arbeiter, Gesamtzeit: ${totalTime} Minuten`;
} else if (taskLog.workers === 1) {
notes += '\n1 Arbeiter';
}
// Find corresponding harvest log if this is a harvesting task
let harvestData = null;
if (taskLog.task === "Harvesting" && dataset.harvest_logs) {
const harvestLog = dataset.harvest_logs.find((h: any) => h.date === taskLog.date);
if (harvestLog) {
harvestData = {
actual_quantity: harvestLog.quantity,
actual_quantity_unit: harvestLog.unit
};
}
}
const taskData: any = {
farm_id: this.farmId,
due_date: taskLog.date,
task_type_id: this.getTaskTypeId(taskType),
duration: taskLog.duration_minutes,
notes: notes.trim(),
complete_date: taskLog.date // Mark as completed
};
// Create base task
const task = await this.client.post<any>('/task', taskData);
// Add harvest data if applicable
if (harvestData && task.task_id) {
try {
await this.client.post(`/task/${task.task_id}/harvest_task`, harvestData);
this.stats.harvestsCreated++;
console.log(` ✓ ${taskLog.task}: ${harvestData.actual_quantity}${harvestData.actual_quantity_unit} (${taskLog.date})`);
} catch (error) {
console.error(` ✗ Failed to add harvest data:`, error);
}
} else {
console.log(` ✓ ${taskLog.task} (${taskLog.date})`);
}
this.stats.tasksCreated++;
} catch (error) {
this.stats.taskErrors++;
console.error(` ✗ Failed to create task ${taskLog.task}:`, error);
}
}
private getTaskTypeId(taskTypeName: string): number {
// Map task type names to IDs
const typeMap: Record<string, number> = {
'plant_task': 1,
'harvest_task': 2,
'field_work_task': 3,
'cleaning_task': 4,
'scouting_task': 5
};
return typeMap[taskTypeName] || 3; // Default to field_work_task
}
private printSummary(): void {
console.log("\n" + "=".repeat(60));
console.log("📊 IMPORT SUMMARY");
console.log("=".repeat(60));
console.log(`\nFarm ID: ${this.stats.farmId}`);
console.log(`\n📍 Locations: ${this.stats.locationsCreated} created, ${this.stats.locationErrors} failed`);
console.log(`📋 Tasks: ${this.stats.tasksCreated} created, ${this.stats.taskErrors} failed`);
console.log(`🌾 Harvests: ${this.stats.harvestsCreated} recorded`);
if (this.stats.warnings.length > 0) {
console.log(`\n⚠️ Warnings (${this.stats.warnings.length}):`);
this.stats.warnings.slice(0, 5).forEach(w => console.log(` - ${w}`));
if (this.stats.warnings.length > 5) {
console.log(` ... and ${this.stats.warnings.length - 5} more`);
}
}
console.log("\n" + "=".repeat(60));
console.log("✨ Import completed!");
console.log("=".repeat(60) + "\n");
}
}
// Main execution
async function main() {
if (!process.env.LITEFARM_EMAIL || !process.env.LITEFARM_PASSWORD) {
console.error("❌ Error: LITEFARM_EMAIL and LITEFARM_PASSWORD must be set in .env file");
process.exit(1);
}
const importer = new TestFarmImporter(
process.env.LITEFARM_EMAIL,
process.env.LITEFARM_PASSWORD
);
try {
await importer.run();
} catch (error) {
console.error("\n💥 Fatal error during import:", error);
process.exit(1);
}
}
// Run if executed directly
main();
export { TestFarmImporter };