# SmartCane Code Merge: Implementation Handoff for Developer
**For**: Martins moltbot
**What**: Complete implementation guide for merging `code-improvements` branch into `master`
**Target Repo**: https://bitbucket.org/sobitnl/smartcane/src/master/
**Date**: February 24, 2026
---
New folder structure, new scripts (R and Python) so new .sh and cron, new buttons (client type and area unit). Add kpi excel to email (in addition to the word file). New packages to be installed. nice to have - group projects by country.
## 📋 TASKS (AI generated, ik weet niet of ze ergens op slaan...)
This handoff includes everything you need to do before handing over to production:
1. Database migration
2. Update Project model
3. Update jobs (2 files)
4. Update ProjectManager form
5. Add form fields to Blade template
6. Update MailingForm
7. Create 5 shell script wrappers
8. Setup Linux cron jobs
9. Run verification tests
10. Country-based project organization (MZ/UG/TZ)
11. Download scheduling (auto-stagger per project)
12. Project search feature
13. Harvest prediction setup
14. Area unit selection UI (hectare/acre)
---
## 📋 WHAT EACH STEP MEANS
### Step 1-6: Laravel Changes
- Steps 1-3: Database + models + jobs (backend logic)
- Steps 4-6: Forms + email logic (UI + user-facing)
### Step 7: Shell Script Wrappers
Create 5 bash shell scripts that wrap R and Python scripts. These let Laravel jobs call R/Python scripts easily.
**Example**: When Laravel needs to create field TIFFs, it runs:
```bash
./10_create_per_field_tiffs.sh --project=angata --area_unit=hectare
```
This script then exports the area unit as an environment variable and calls the R script.
### Step 8: Setup Cron
Add scheduled job to Linux to run `/10_planet_download.sh` every day at staggered times (00:01, 00:11, etc.) for each project.
### Step 10: Verify Everything Works
Run manual tests to confirm all changes work correctly.
---
## 🎯 Client Type Overview (Read This First)
SmartCane supports two project types. **Which one you implement determines which scripts to use:**
| Project Type | Example | Scripts Used | Purpose |
|--------------|---------|--------------|---------|
| **agronomic_support** | aura, chemba, esa | not 21, 22, 23, 31; but the rest yes | Weekly field health analysis, CI trends, uniformity alerts |
| **cane_supply** | angata, simba | Additional: 21, 22, 23, 31 | Field health + **harvest date prediction** (ML-based) |
**Translation:**
- Choose `agronomic_support` if you want field-level vegetation analysis only
- Choose `cane_supply` if you also want harvest date forecasting (requires ML models)
All the code below supports **both** types. The database column `client_type` controls which scripts actually run.
---
### STEP 1: Database Migration
Create file: `database/migrations/2025_02_24_add_client_type_and_area_unit_to_projects_table.php`
```php
enum('client_type', ['agronomic_support', 'cane_supply'])
->default('agronomic_support')
->after('download_path');
$table->enum('preferred_area_unit', ['hectare', 'acre'])
->default('hectare')
->after('client_type');
});
}
public function down(): void
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn(['client_type', 'preferred_area_unit']);
});
}
};
```
Run migration:
```bash
php artisan migrate
```
✅ **Expected**: Two new columns added to projects table.
---
### STEP 2: Update Project Model
**File**: `laravel_app/app/Models/Project.php`
#### 2.1: Update `$fillable` array
Add these two lines to the fillable declaration:
```php
protected $fillable = [
'name',
'download_path',
'client_type', // ← ADD THIS
'preferred_area_unit', // ← ADD THIS
'mail_template',
'mail_subject',
'mail_frequency',
'mail_day',
'mail_scheduled',
'pivot_json_path',
'span_json_path',
'harvest_json_path',
'min_harvest_date',
'borders',
];
```
#### 2.2: Add new method at bottom of Project class
```php
/**
* Get the latest KPI Excel file for this project
*/
public function getLatestKpiFile(): ?string
{
$kpiPath = $this->download_path . '/reports/kpis/';
try {
$files = Storage::files($kpiPath);
} catch (\Exception $e) {
return null;
}
return collect($files)
->filter(fn($f) => Str::endsWith($f, '.xlsx'))
->sortByDesc(fn($f) => Storage::lastModified($f))
->first();
}
```
---
### STEP 3: Update Jobs
#### 3.1: ProjectDownloadTiffJob.php
**File**: `laravel_app/app/Jobs/ProjectDownloadTiffJob.php`
Find this line (around line 73):
```php
$path = $project->download_path . '/merged_final_tif/' . $filename;
```
Replace with:
```php
$path = $project->download_path . '/field_tiles_CI/' . $filename;
```
#### 3.2: ProjectMosiacGeneratorJob.php
**File**: `laravel_app/app/Jobs/ProjectMosiacGeneratorJob.php`
Find the `$command` array (around lines 50-60):
```php
$command = [
sprintf('%sbuild_mosaic.sh', $projectFolder),
sprintf('--end_date=%s', $this->mosaic->end_date->format('Y-m-d')),
sprintf('--offset=%s', $this->mosaic->offset),
sprintf('--data_dir=%s', $this->mosaic->project->download_path),
sprintf('--file_name_tif=%s', basename($this->mosaic->path)),
];
```
Replace with:
```php
$command = [
sprintf('%s40_mosaic_creation_per_field.sh', $projectFolder),
sprintf('--project=%s', $project->name),
sprintf('--end_date=%s', $this->mosaic->end_date->format('Y-m-d')),
sprintf('--offset=%s', $this->mosaic->offset),
];
```
Also improve exception handling. Find the try/catch block and replace with:
```php
try {
$process = ProcessNew::timeout(300)
->env(['PATH' => $currentPath.':/usr/local/Cellar/pandoc/3.1.8/bin/pandoc'])
->start($command, function (string $type, string $output) use ($project) {
ProjectLogger::log($project, $output);
$this->throwIfOutputContainsError($output);
});
$results = $process->wait();
if ($results->successful()) {
$this->mosaic->setStatusSuccess();
}
} catch (\RuntimeException|\Symfony\Component\Process\Exception\ProcessTimedOutException|\Symfony\Component\Process\Exception\ProcessFailedException $e) {
ProjectLogger::log($project, "MOSAIC JOB ERROR: " . $e->getMessage());
$this->mosaic->setStatusFailed();
throw $e;
}
```
---
### STEP 4: Update ProjectManager Form
**File**: `laravel_app/app/Livewire/Projects/ProjectManager.php`
#### 4.1: Update `createProject()` method
Find the method and replace with this version that creates directories:
```php
public function createProject()
{
$projectIdentifier = $this->formData['id'] ?? null;
Validator::make(
['name' => $this->formData['name']],
['name' => ['required', Rule::unique('projects')->ignore($projectIdentifier), 'string', 'max:255']]
)->validate();
$basePath = $this->makeValidDirectoryName($this->formData['name']);
// Create base directory
Storage::makeDirectory($basePath, recursive: true);
// Create project record
$project = Project::create([
'name' => $this->formData['name'],
'download_path' => $basePath,
'client_type' => $this->formData['client_type'] ?? 'agronomic_support',
'preferred_area_unit' => $this->formData['preferred_area_unit'] ?? 'hectare',
]);
// Create required subdirectories
$subdirectories = [
'Data',
'merged_tif',
'field_tiles',
'field_tiles_CI',
'daily_vals',
'weekly_mosaic',
'reports',
'reports/kpis',
'logs',
'RGB',
];
foreach ($subdirectories as $subdir) {
Storage::makeDirectory($basePath . '/' . $subdir, recursive: true);
}
return redirect()->route('project.show', [$project->name, 'settings']);
}
```
#### 4.2: Update `resetFormData()` method
Add the two new fields:
```php
private function resetFormData()
{
$this->formData = [
'name' => '',
'client_type' => 'agronomic_support', // ← ADD THIS
'preferred_area_unit' => 'hectare', // ← ADD THIS
'mail_template' => '',
'mail_subject' => '',
'mail_frequency' => '',
'mail_day' => '',
'mail_scheduled' => false,
// ... rest of existing fields
];
}
```
---
### STEP 5: Add Form Fields to Blade Template
**File**: Your project create/edit form Blade template (e.g., `resources/views/livewire/projects/project-manager.blade.php`)
Add these two fields **immediately after the project name field**:
```blade
```
**Step 4: Pass preference to R scripts** (in job/shell wrapper)
When launching R scripts, read `project->preferred_area_unit` and either:
- **Option A**: Write to `parameters_project.R` dynamically before script execution
- **Option B**: Pass as environment variable to scripts (R scripts read `Sys.getenv("AREA_UNIT")`)
Example (PowerShell wrapper):
```powershell
$areaUnit = $project->preferred_area_unit # From database
$env:AREA_UNIT = $areaUnit # Set environment variable
& "C:\Program Files\R\R-4.4.3\bin\x64\Rscript.exe" r_app/80_calculate_kpis.R $project
```
**Testing checklist**:
- [ ] Database migration runs successfully
- [ ] Project form shows area unit radio buttons
- [ ] Can select and save area unit preference
- [ ] Area unit persists in database
- [ ] Run script 80 with one project set to "hectare", observe KPI output
- [ ] Run script 80 with another project set to "acre", compare outputs
- [ ] Reports display area in user's chosen unit
**Notes**:
- Default preference: "hectare" (metric standard)
- Conversion factor: 0.404686 (1 hectare = 0.404686 acres)
- All area calculations use EPSG:6933 (equal-area projection) for accuracy
- Area column in KPI outputs named dynamically: "Area_ha" or "Area_ac"
---
# SECTION C: POST-MERGE DATA MIGRATION STRATEGY
## Approach: Per-Project Options
### Small Projects
**Projects**: aura, chemba, xinavane, esa, simba, john, huss, tpc, miwani, etc.
**Option A: Delete & Redownload** ✅ Recommended (Fastest setup)
1. Backup project folder externally (optional)
2. Delete project from Laravel UI (removes all data)
3. Recreate project with new form fields (client_type + area_unit selector)
4. Redownload satellite data for relevant date range
5. Run full R pipeline (Scripts 10-80) to generate KPI reports
### Large Projects
**Projects**: angata
**Option B: Preserve merged_tif** ✅ Recommended (No redownload)
1. Backup `merged_tif/` folder externally
2. Keep ONLY: `merged_tif/` folder
3. Delete: everything else (field_tiles/, reports/, logs/, etc.)
4. Run Scripts 10-80 from existing merged_tif data
- Regenerates field_tiles_CI/, reports/kpis/, weekly_mosaic/, etc.
- No satellite redownload needed
- KPI reports automatically use project's preferred_area_unit
---