HANDBOOK_GUIDE2026-06-29
OptiCrop: Smart Agricultural Production Optimization Engine ML Guide
By Rayan Syed
45 min read
Part 7: Flask API Backend
Save this script as app.py in your project folder.
7.1 Understanding Backend Web Servers & API Routing
In this phase, we build a local web backend using Flask.
- API Routing: Routing is the mechanism that maps network URLs (endpoints) to specific Python functions. In Flask, we define routes using
@app.route(). - JSON Exchange: JavaScript on the frontend will capture user entries and package them as JSON. The backend extracts this JSON (
request.get_json()), converts the values to floats/integers, and packs them into a NumPy array matching the exact structure used during model training. - Making Predictions: The Flask server loads our serialized model weights (
joblib.load('models/crop_model.joblib')) in memory. When it receives a request, it callsmodel.predict()and returns the recommended crop label back to the client. - Why We Need a Server: You might wonder: why can’t we run predictions directly in browser JavaScript? In real-world applications, machine learning models can weigh several hundred megabytes. Loading these models directly into the user’s web browser would freeze their system or crash the page. Running the model on a dedicated backend server keeps the client lightweight and secure.
from flask import Flask, request, jsonify, render_template
import joblib
import numpy as np
import os
app = Flask(__name__)
# Load the saved model and scaler
MODEL_PATH = 'models/crop_model.joblib'
SCALER_PATH = 'models/scaler.joblib'
if not os.path.exists(MODEL_PATH) or not os.path.exists(SCALER_PATH):
print("ERROR: Model or Scaler not found! Please run train.py first.")
exit(1)
model = joblib.load(MODEL_PATH)
scaler = joblib.load(SCALER_PATH)
@app.route('/')
def home():
return render_template('index.html')
@app.route('/predict', methods=['POST'])
def predict():
try:
data = request.get_json()
# Build features list in the exact order the model expects:
# N, P, K, temperature, humidity, ph, rainfall
features_raw = np.array([[
float(data['N']),
float(data['P']),
float(data['K']),
float(data['temperature']),
float(data['humidity']),
float(data['ph']),
float(data['rainfall'])
]])
# Apply boundary checks
n, p, k, temp, hum, ph, rain = features_raw[0]
if not (0 <= n <= 250): return jsonify({'error': 'Nitrogen must be between 0 and 250 mg/kg.'}), 400
if not (0 <= p <= 250): return jsonify({'error': 'Phosphorus must be between 0 and 250 mg/kg.'}), 400
if not (0 <= k <= 300): return jsonify({'error': 'Potassium must be between 0 and 300 mg/kg.'}), 400
if not (-10 <= temp <= 60): return jsonify({'error': 'Temperature must be between -10°C and 60°C.'}), 400
if not (0 <= hum <= 100): return jsonify({'error': 'Humidity must be between 0% and 100%.'}), 400
if not (0 <= ph <= 14): return jsonify({'error': 'Soil pH must be between 0.0 and 14.0.'}), 400
if not (0 <= rain <= 1000): return jsonify({'error': 'Rainfall must be between 0mm and 1000mm.'}), 400
# Apply the StandardScaler fitted during training
features_scaled = scaler.transform(features_raw)
# Predict optimal crop
prediction = model.predict(features_scaled)[0]
return jsonify({
'success': True,
'crop': str(prediction).capitalize()
})
except Exception as e:
return jsonify({'error': str(e)}), 400
if __name__ == '__main__':
app.run(port=5000, debug=True)
Checkpoint: Backend Complete
At this stage, your local Flask backend is complete and ready to receive soil applications. However, if you visit the server now, you will see a TemplateNotFound error. This is because we haven’t created the frontend form files yet! In the next part, we will build the user interface files (index.html, style.css, and script.js) that will talk to this Flask backend.