# SOBIT Linux Server Deployment Architecture

This document explains how SmartCane runs on SOBIT (the production Linux server) through Laravel web interface, contrasting with manual dev laptop execution.

## High-Level SOBIT Architecture

SOBIT hosts a Laravel-based web application that orchestrates Python and R pipeline execution through a **job queue system**. When a user clicks a button in the web UI, a job is created, pushed to a queue, and workers execute the actual Python/R scripts.

```mermaid
%% SOBIT Deployment Architecture
flowchart TD
    A["Web UI<br/>Laravel Dashboard<br/>Project Controller"] 
    
    B["User Action<br/>e.g., Download Data,<br/>Generate Report"]
    
    C["Job Dispatch<br/>ProjectDownloadTiffJob,<br/>ProjectMosaicJob, etc."]
    
    D["Queue Worker<br/>Executes Job Handler"]
    
    E["Shell Script Wrapper<br/>runpython.sh<br/>20_ci_extraction.sh<br/>90_kpi_report.sh"]
    
    F["Python/R Executable<br/>00_download_8band_pu_optimized.py<br/>20_ci_extraction_per_field.R<br/>rmarkdown::render"]
    
    G["Output Files<br/>laravel_app/storage/app/{PROJECT}/"]
    
    H["Next Job Dispatch<br/>job chaining"]
    
    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G
    G --> H
    H -.-> D
```

---

## Laravel Job Classes & Queue Flow

SmartCane uses Laravel's **queue system** to manage asynchronous task execution. Each stage has a corresponding Job class.

### Job Class Hierarchy

| Job Class | Shell Script | R/Python Script | Input File | Output File | Next Stage |
|-----------|-------------|-----------------|------------|------------|-----------|
| **ProjectDownloadTiffJob** | `runpython.sh` | `00_download_8band_pu_optimized.py` | date param | `merged_tif/{DATE}.tif` | ProjectCreateFieldTilesJob |
| **ProjectCreateFieldTilesJob** | (direct Rscript) | `10_create_per_field_tiffs.R` | `merged_tif/{DATE}.tif` | `field_tiles/{FIELD}/{DATE}.tif` | ProjectCIExtractionJob |
| **ProjectCIExtractionJob** | `20_ci_extraction.sh` | `20_ci_extraction_per_field.R` | `field_tiles/...` | `combined_CI_data.rds` | ProjectGrowthModelJob |
| **ProjectGrowthModelJob** | `30_growth_model.sh` | `30_interpolate_growth_model.R` | `combined_CI_data.rds` | `All_pivots_...rds` | ProjectMosaicGeneratorJob |
| **ProjectMosaicGeneratorJob** | `40_mosaic_creation.sh` | `40_mosaic_creation_per_field.R` | `field_tiles_CI/...` | `weekly_mosaic/{FIELD}/week_*.tif` | ProjectKPICalculationJob |
| **ProjectKPICalculationJob** | `80_calculate_kpis.sh` | `80_calculate_kpis.R` | `weekly_mosaic/...` | `field_analysis_*.xlsx` + RDS | ProjectReportGeneratorJob |
| **ProjectReportGeneratorJob** | `90_kpi_report.sh` | `90_*.Rmd` or `91_*.Rmd` render | Excel + RDS | `SmartCane_Report_*.docx` | ✅ Complete |

---

## Execution Flow: From Web UI to Output

### Scenario: User Clicks "Download Latest Data" for Project "Angata"

#### Step 1: Web UI Dispatch

**File**: `laravel_app/app/Http/Controllers/ProjectController.php`

```php
public function downloadData(Request $request, $projectName)
{
    $project = Project::where('name', $projectName)->firstOrFail();
    
    // Dispatch job to queue
    dispatch(new ProjectDownloadTiffJob($project, $request->date));
    
    return response()->json(['status' => 'Job queued']);
}
```

#### Step 2: Job Queue Entry

**File**: `laravel_app/app/Jobs/ProjectDownloadTiffJob.php`

```php
class ProjectDownloadTiffJob implements ShouldQueue
{
    public $project;
    public $date;
    
    public function __construct(Project $project, $date = null)
    {
        $this->project = $project;
        $this->date = $date ?? Carbon::now()->toDateString();
    }
    
    public function handle()
    {
        // Execute shell script wrapper
        $command = "bash " . base_path() . "/runpython.sh " .
                   "--date=" . $this->date . " " .
                   "--project_dir=" . $this->project->directory;
        
        $process = new Process(explode(' ', $command));
        $process->run();
        
        if ($process->isSuccessful()) {
            // Dispatch next job
            dispatch(new ProjectCreateFieldTilesJob($this->project, $this->date));
        } else {
            // Log error and notify user
            Log::error("Download failed for {$this->project->name}: " . $process->getErrorOutput());
        }
    }
}
```

#### Step 3: Shell Script Execution (runpython.sh)

**File**: `laravel_app/../runpython.sh`

```bash
#!/bin/bash
# Wrapper for Python download script

DATE=${1#--date=}
PROJECT_DIR=${2#--project_dir=}

cd /path/to/SmartCane_code/python_app

# Run Python with project-specific environment
python 00_download_8band_pu_optimized.py $PROJECT_DIR --date $DATE

if [ $? -eq 0 ]; then
    echo "Download successful for $PROJECT_DIR on $DATE"
    exit 0
else
    echo "Download failed for $PROJECT_DIR on $DATE"
    exit 1
fi
```

#### Step 4: Python Download (Stage 00)

**File**: `python_app/00_download_8band_pu_optimized.py`

```python
import sys
from datetime import datetime

project_dir = sys.argv[1]  # "angata"
date_str = sys.argv[2] if len(sys.argv) > 2 else datetime.now().strftime('%Y-%m-%d')

# Authenticate with Planet API
auth = get_planet_credentials()
bbox = get_project_bbox(project_dir)  # From database or config

# Download 4-band TIFF
tiff_path = f"laravel_app/storage/app/{project_dir}/merged_tif/{date_str}.tif"
download_and_save_tiff(auth, bbox, date_str, tiff_path)

print(f"Downloaded to {tiff_path}")
```

#### Step 5: Job Chaining (Automatic)

**Back in ProjectDownloadTiffJob.php**:

```php
if ($process->isSuccessful()) {
    // Dispatch next job in pipeline
    dispatch(new ProjectCreateFieldTilesJob($this->project, $this->date));
}
```

This triggers `ProjectCreateFieldTilesJob`, which calls the next shell script, and so on.

---

## Shell Script Wrappers: Design & Responsibility

SOBIT wrapper scripts ensure Python and R scripts run with correct environment variables and working directory.

### Root-Level Shell Scripts

**Location**: `c:\Users\timon\Documents\SmartCane_code\` (root)

| Script | Purpose | Calls | Environment Setup |
|--------|---------|-------|-------------------|
| `10_planet_download.sh` | Stage 10 wrapper | `Rscript 10_create_per_field_tiffs.R` | Sets R_LIBS, PYTHONPATH |
| `20_ci_extraction.sh` | Stage 20 wrapper | `Rscript 20_ci_extraction_per_field.R` | R environment + data paths |
| `30_growth_model.sh` | Stage 30 wrapper | `Rscript 30_interpolate_growth_model.R` | Growth model data path |
| `40_mosaic_creation.sh` | Stage 40 wrapper | `Rscript 40_mosaic_creation_per_field.R` | Sentinel config for mosaics |
| `80_calculate_kpis.sh` | Stage 80 wrapper | `Rscript 80_calculate_kpis.R` | KPI utility loading |
| `90_kpi_report.sh` | Stage 90/91 wrapper | `rmarkdown::render()` via Rscript | RMarkdown dependencies |
| `runpython.sh` | Python wrapper | `python 00_download_8band_pu_optimized.py` | Python venv activation |

### Example: `20_ci_extraction.sh`

```bash
#!/bin/bash
# CI Extraction wrapper for SOBIT

set -e  # Exit on error

# Load environment
export R_LIBS="/opt/R/library"
export PYTHONPATH="/opt/python/lib/python3.9/site-packages:$PYTHONPATH"
PROJECT_DIR=$1
END_DATE=$2
OFFSET=${3:-7}

# Change to code directory
cd /var/www/smartcane/code

# Execute R script
Rscript r_app/20_ci_extraction_per_field.R "$PROJECT_DIR" "$END_DATE" "$OFFSET"

if [ $? -eq 0 ]; then
    echo "[$(date)] CI extraction completed for $PROJECT_DIR"
    # Log success to Laravel job tracking
    echo "Success" > "laravel_app/storage/logs/${PROJECT_DIR}_stage20_${END_DATE}.log"
else
    echo "[$(date)] CI extraction FAILED for $PROJECT_DIR"
    exit 1
fi
```

### Example: `90_kpi_report.sh`

```bash
#!/bin/bash
# RMarkdown report rendering wrapper

set -e

PROJECT_DIR=$1
REPORT_DATE=$2
CLIENT_TYPE=$3  # "agronomic_support" or "cane_supply"

cd /var/www/smartcane/code

# Determine which RMarkdown template to use
if [ "$CLIENT_TYPE" = "agronomic_support" ]; then
    REPORT_TEMPLATE="r_app/90_CI_report_with_kpis_agronomic_support.Rmd"
else
    REPORT_TEMPLATE="r_app/91_CI_report_with_kpis_cane_supply.Rmd"
fi

# Render RMarkdown to Word
Rscript -e "
  rmarkdown::render(
    '$REPORT_TEMPLATE',
    params = list(
      data_dir = '$PROJECT_DIR',
      report_date = as.Date('$REPORT_DATE')
    ),
    output_file = 'SmartCane_Report_${CLIENT_TYPE}_${PROJECT_DIR}_${REPORT_DATE}.docx',
    output_dir = 'laravel_app/storage/app/$PROJECT_DIR/reports'
  )
"

echo "[$(date)] Report generation completed for $PROJECT_DIR"
```

---

## Data Storage on SOBIT

All data is stored in Laravel's standard storage directory with project-based subdirectories.

### Storage Structure

```
laravel_app/storage/app/
├── angata/                          # Cane Supply project
│   ├── merged_tif/                  # Stage 00 Python output
│   │   ├── 2026-02-12.tif
│   │   └── 2026-02-19.tif
│   ├── field_tiles/                 # Stage 10 output
│   ├── field_tiles_CI/              # Stage 20 output
│   ├── Data/
│   │   ├── pivot.geojson            # Field boundaries (input)
│   │   ├── harvest.xlsx             # Harvest dates (input)
│   │   ├── extracted_ci/            # Stage 20 CI data
│   │   └── growth_model_interpolated/  # Stage 30 data
│   ├── weekly_mosaic/               # Stage 40 output
│   └── reports/                     # Stages 80/90/91 output
│       ├── SmartCane_Report_*.docx
│       ├── angata_field_analysis_week*.xlsx
│       └── kpis/
│           └── *.rds
│
├── aura/                            # Agronomic Support project
│   └── (same structure as angata)
│
├── chemba/
├── xinavane/
├── esa/
└── simba/
```

### Permissions Model

- **Web server user** (www-data on Linux): Can read/write to all storage subdirectories
- **Laravel artisan commands**: Have full access via `Storage::disk('local')`
- **Job queue workers**: Execute as www-data, access via storage symlink

---

## Job Queue Configuration

**File**: `laravel_app/config/queue.php`

```php
'default' => env('QUEUE_CONNECTION', 'database'),

'connections' => [
    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 1800,  // 30 min timeout per job
        'after_commit' => false,
    ],
    
    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
        'after_commit' => false,
    ],
],
```

### Starting Queue Worker

On SOBIT, the queue worker runs in the background:

```bash
# Manual start (for debugging)
php artisan queue:work --queue=default --timeout=1800

# Production (supervisor-managed)
# Supervisor config: /etc/supervisor/conf.d/smartcane-worker.conf
[program:smartcane-queue-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/smartcane/artisan queue:work --queue=default --timeout=1800
autostart=true
autorestart=true
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/smartcane-queue.log
```

---

## Error Handling & Retries

### Job Failure Scenario

If a shell script returns non-zero exit code:

```php
// In job handle() method
if (!$process->isSuccessful()) {
    Log::error("Job failed: " . $process->getErrorOutput());
    
    // Laravel automatically retries if $tries > 1
    // Configurable in job class:
    // public $tries = 3;
    // public $retryAfter = 300;  // 5 minutes between retries
}
```

### Monitoring & Alerts

- **Failed Jobs Table**: `jobs_failed` table in Laravel database
- **Logs**: `storage/logs/laravel.log` + individual stage logs
- **User Notification**: Job status visible in web UI; email alerts can be configured

---

## Deployment vs Dev Laptop: Key Differences

| Aspect | **SOBIT (Production)** | **Dev Laptop** |
|--------|----------------------|----------------|
| **Execution Model** | Async job queue | Synchronous PowerShell |
| **User Interaction** | Web UI clicks → jobs | Manual script calls |
| **Data Location** | `laravel_app/storage/app/{PROJECT}/` | Same (shared Laravel directory) |
| **Error Handling** | Job retries, logs in database | Terminal output only |
| **Parallelization** | Multiple queue workers | Single sequential execution |
| **Monitoring** | Web dashboard + Laravel logs | Console output only |
| **Environment Setup** | Bash scripts set env vars | Manual R/Python environment |
| **Scheduling** | Can use Laravel scheduler for automated runs | Manual cron or batch scripts |

---

## Running Full Pipeline on SOBIT via Web UI

### User Workflow

1. **Navigate** to ProjectController dashboard
   ```
   http://sobit-server/projects/angata
   ```

2. **Click** "Download Latest Data" button
   - ProjectDownloadTiffJob queued with current date
   - Web UI shows "Job submitted"

3. **Queue Worker** (background process) executes jobs in sequence
   - Downloads TIFF (Stage 00)
   - Dispatches Stage 10 job
   - Creates field tiles
   - Dispatches Stage 20 job
   - (etc. through Stage 91)

4. **Monitor** progress via Dashboard
   - Job history tab shows completed jobs
   - Report links appear when Stage 91 completes
   - Download Word/Excel from reports section

### Command-Line Submission (Alternative)

Developer can manually trigger jobs via Laravel artisan:

```bash
# SSH into SOBIT
ssh user@sobit-server

# Manually dispatch job
php artisan smartcane:process-pipeline angata --date=2026-02-19 --async

# Or using job dispatch directly
php artisan queue:work --queue=default
```

---

## Troubleshooting SOBIT Deployment

### Issue: Job Stuck in Queue

```bash
# Check job queue depth
SELECT COUNT(*) FROM jobs WHERE queue = 'default';

# Retry failed jobs
php artisan queue:retry --all

# Clear old jobs
php artisan queue:clear
```

### Issue: Shell Script Can't Find R/Python

**Cause**: Environment variables not set in shell wrapper.

**Fix**: Add to shell script:
```bash
export PATH="/opt/R/bin:/opt/python/bin:$PATH"
export R_HOME="/opt/R"
source /opt/python/venv/bin/activate
```

### Issue: Permission Denied on Storage Files

**Cause**: Files created by web server, permission mismatch.

**Fix**:
```bash
sudo chown -R www-data:www-data laravel_app/storage/app/*
sudo chmod -R 755 laravel_app/storage/app/
```

---

## Next Steps

- See [ARCHITECTURE_DATA_FLOW.md](ARCHITECTURE_DATA_FLOW.md) for stage details
- See [CLIENT_TYPE_ARCHITECTURE.md](CLIENT_TYPE_ARCHITECTURE.md) for how jobs route based on project type
- See [DEV_LAPTOP_EXECUTION.md](DEV_LAPTOP_EXECUTION.md) for alternative manual execution model
- See [ARCHITECTURE_INTEGRATION_GUIDE.md](ARCHITECTURE_INTEGRATION_GUIDE.md) for choosing SOBIT vs dev laptop
