Codebase study guide

Krishi AI

A web app that helps farmers in Kerala. It recommends fertilizer, finds plant disease in a leaf photo, and answers farm questions in a chat. This guide explains how each part works, shows where data moves, and lists the questions an interviewer is likely to ask.

Repo: MeSanskarSingh/krishi-ai Read at commit: c441c6d (29 Mar 2026) Live app: krishi-ai-sigma.vercel.app Style: ASD-STE100
How to use this guide

Read chapters 1 to 8 first. They explain the product and the three AI features. Chapters 12 and 13 show where the code is different from the README and which parts are weak. Read them before an interview, because a good interviewer will find these points. Chapter 14 holds practice questions. Click a question to show the answer.

1 · Overview

What the product does, in simple words.

Krishi AI is a farm assistant in the browser. The name means “farming AI” in Hindi. The landing page calls it “Your Digital Krishi Sathi” (farm companion). The data and the models are for Kerala: Kerala soils, Kerala crops and the three Kerala seasons.

The app has three AI features. Each feature is a separate Python API:

  • Fertilizer recommendation. The farmer enters soil type, crop, season, soil pH and the N, P and K values. Two Random Forest models give the fertilizer name and the quantity in kg per acre.
  • Disease detection. The farmer uploads a photo of a leaf. A CNN picks one of 51 classes, for example Rice Brown spot, and gives a confidence value.
  • Kisan Mitra chatbot. The farmer types a question. A hosted LLM (Mistral 7B) writes a short plain-text answer.

The dashboard also shows a 6-day weather forecast for Kottayam, links to government schemes, and a market price table. The market price table contains fixed demo data.

The 30-second pitch

“Krishi AI is a Next.js front end with three FastAPI microservices. The fertilizer service loads a pickled Random Forest classifier and regressor that we trained on Kerala soil data. The disease service runs a Keras CNN on a 128 × 128 leaf image and returns one of 51 crop-disease classes. The chatbot service calls Mistral-7B-Instruct through the Hugging Face Inference API with a system prompt for Indian farmers. The front end is on Vercel. Each API is a separate service on Render, so we can deploy and scale each model alone.”

Programs1 web app + 3 APIsNext.js, three FastAPI services
Front endNext.js 16Pages Router · React 19 · Tailwind 3
Fertilizer modelRandom Forest × 2100 trees each · scikit-learn 1.6.1
Disease modelKeras CNN128 × 128 RGB in · 51 classes out
Chatbot LLMMistral-7B-Instruct v0.2Hugging Face Inference API
HostingVercel + RenderRender free plan for the APIs
RegionKerala5 soils · 7 crops · 3 seasons
DatabaseNoneNo users, no saved history

What the app does not do

  • It has no login and no user accounts.
  • It does not save predictions, photos or chats.
  • It does not get live market prices.
  • It does not change the weather city. The city is always Kottayam.
  • It does not translate the pages into Malayalam yet. The translation files exist, but the app does not use them.
Caution

The app footer says: “Predictions are for assistance, not substitutes for expert advice.” Say this in an interview. A wrong fertilizer dose or a wrong disease name can cause crop loss.

2 · System map

Which program talks to which.

Vercel serves the Next.js build HTML + JS Browser React 19 · Tailwind · MUI icons / /dashboard /fertilizer /disease_detect /chatbot /market_prices /mission API URLs come from NEXT_PUBLIC_* (built into the bundle) Render · free plan · FastAPI + Uvicorn Loaded once at startup Fertilizer API POST /predict 7 numbers in · name + kg/acre out Disease API POST /predict leaf image in · class + % out Chatbot API POST /chat message in · plain text out fertilizer_system_kerala.pkl RF classifier + RF regressor StandardScaler · LabelEncoder disease_cnn.h5 Keras CNN · 5.0 MB TensorFlow 2.10 Hugging Face Mistral-7B-Instruct-v0.2 needs HF_TOKEN JSON multipart image JSON pickle.load load_model HF API OpenWeatherMap 5-day forecast · key in client code GET forecast /api/predict unused · calls localhost:8000
The browser calls each API directly. The Next.js server does not proxy the calls. The API base URLs are NEXT_PUBLIC_* values, so they are visible in the JavaScript bundle. Each API allows CORS only from localhost:3000 and the Vercel domain. Dashed box = third-party service or dead code.

What each program owns

ProgramFolderJob
Web appsrc/, public/Shows the landing page, dashboard and the feature pages. Sends form data, images and chat text to the APIs. Gets the weather from OpenWeatherMap.
Fertilizer APIbackend/fertilizer_api/Loads the pickle file at startup. Scales the 7 inputs. Returns the fertilizer name and the quantity in kg/acre.
Disease APIbackend/disease_api/Loads the CNN at startup. Resizes the image to 128 × 128. Returns the top class and its confidence in percent.
Chatbot APIbackend/chatbot_api/Calls Mistral 7B on Hugging Face with a fixed system prompt. Returns the answer text. Stops at startup if HF_TOKEN is not set.

Why microservices

The three models need different Python stacks. TensorFlow 2.10 runs only on Python 3.10 or older. The fertilizer pickle needs scikit-learn 1.6.1 and NumPy 2, and its .python-version file says Python 3.13.1. One process cannot easily hold both stacks. Separate services also keep a crash in one model away from the others.

The cost is three cold starts on the Render free plan, three CORS lists to keep the same, and three URLs to configure in the front end.

3 · Repository layout

Every folder and the important files. The repo has one commit.

PathWhat it contains
src/pages/index.jsLanding page. Renders the navbar and the hero section.
src/pages/dashboard.jsWeather card, scheme links and the four feature cards.
src/pages/fertilizer.jsFertilizer form, result box and the “Reference Manual” pop-up with the input codes.
src/pages/disease_detect.jsDrag-and-drop image upload and the result text box.
src/pages/chatbot.jsChat window for Kisan Mitra.
src/pages/market_prices.jsTable of five fixed demo prices.
src/pages/mission.jsMission text on a background image.
src/pages/_app.jsWraps every page with the navbar and appWithTranslation.
src/pages/_document.jsAdds Font Awesome 5.15.4 from cdnjs and the favicon.
src/pages/components/navbar.js, HeroSection.js, featureCard.js, weatherCard.js.
src/pages/personalStyles/CSS modules for the navbar and hero.
src/pages/api/predict.jsNext.js API route that forwards an image to localhost:8000. No page uses it.
src/data/cardsData.jsTitle, icon, text and link for each feature card.
src/styles/globals.cssTailwind layers and three local fonts: Mochiy Pop P One, Mitr, Merriweather Sans.
public/images/Logo, farmer illustrations and feature icons.
public/locales/en|ml/English and Malayalam strings. The keys in the two files are different.
backend/fertilizer_api/fertilizer_api.py, the 6.2 MB pickle, pinned requirements, Python 3.13.1.
backend/disease_api/disease_api.py, disease_cnn.h5 (5.0 MB), TensorFlow 2.10, Python 3.10.0.
backend/chatbot_api/chatbot_api.py, requirements, Python 3.10.0.
backend/ (root files)disease_cnn.keras (20 MB, a larger model, not used), history_kerala.csv (one row), sample-leaf.jpeg, old requirements.txt and __pycache__ for main.py and api.py. The source files for those two are not in the repo.
render.yamlRender blueprint for the chatbot service only.
next-i18next.config.jsLocales en and ml. Default en.
tailwind.config.jsColour greenfooter #3F9148, background #FCF9F6, three font families.

4 · Frontend pages

Next.js Pages Router. All data calls run in the browser.

Routes

RoutePage titleWhat the user seesCalls
/Krishi AI - Smart Farming AssistantHero text “Your Digital Krishi Sathi”, a farmer picture, the “LET’S GET STARTED” button, and a green footer with a caution line.—
/dashboardKrishi AI DashboardLeft panel: weather card and scheme links. Main area: four feature cards.OpenWeatherMap
/fertilizerFertilizer PredictionSeven number fields, a submit button, and a pop-up with the code table.Fertilizer API
/disease_detectCrop Disease DetectionDrop zone, image preview and a read-only result box.Disease API
/chatbotKisan Mitra ChatbotMessage list and an input with a send button. Enter also sends.Chatbot API
/market_pricesMarket Price PredictionFive rows: crop, variety, market, unit, price in ₹, trend arrow.—
/missionOur Mission at Krishi AIThree paragraphs about the mission and future plans.—

Shared layout

  • _app.js renders <Navbar /> above each page. The navbar is fixed at the top. It has the logo, three links (Home, Features, Our Mission) and a language menu.
  • The navbar marks the active link with green text and a green underline. It uses router.pathname for this.
  • The language menu has one item, English. It only changes local state. It does not change the locale.
  • The feature cards come from cardsData.js. Each card is a Link with a hover zoom.
  • Icons come from MUI (@mui/icons-material). The hero button uses a Font Awesome icon through CSS.

Visual style

The palette is cream (#FEF7EB), brown (#5A3C34, #462f27), sand (#e2c5a7) and field green (#3F9148). Headings use Mochiy Pop P One. Body text uses Mitr and Merriweather Sans. All three fonts are local .ttf files in public/fonts.

State in each page

Each page uses only useState and useEffect. There is no global store, no React Query and no context. Nothing persists after a reload.

Note

The components/ and personalStyles/ folders are inside src/pages/. In the Pages Router, every .js file in pages/ becomes a route. So /components/navbar is also a page. Move these folders to src/components/. The Tailwind config already scans src/components/.

Translation (i18n)

The repo installs next-i18next, i18next and react-i18next. _app.js uses appWithTranslation. But no page calls serverSideTranslations, next.config.mjs has no i18n block, and no page calls t(). The English file has 7 keys. The Malayalam file has 9 keys, and only 3 keys are the same. The result: all text is hard-coded English.

5 · Fertilizer advice

From seven numbers to a fertilizer name and a dose.

Procedure: get a fertilizer recommendation

  1. Open /fertilizer.
  2. Click View Reference Manual. Find the codes for your soil, crop and season.
  3. Type the soil code (0 to 4), the crop code (0 to 6) and the season code (0 to 2).
  4. Type the soil pH. Type the nitrogen (N), phosphorus (P) and potassium (K) values from your soil test.
  5. Click Get Recommendation. The button shows “Predicting...” while it waits.
  6. Read the result in the green box, for example Urea — 170.03 kg/acre.
Caution

Use only the codes in the manual. The API does not check the ranges. A wrong code still gives a result, and that result has no meaning.

Input codes

FieldCode → value
Soil type0 Laterite Soil · 1 Coastal Alluvium · 2 Sandy Loam · 3 Forest Loam · 4 Red Soil
Crop type0 Rice (Paddy) · 1 Coconut · 2 Rubber · 3 Pepper · 4 Cardamom · 5 Cashew · 6 Banana
Season0 Virippu (Kharif) · 1 Mundakan (Rabi) · 2 Puncha (Summer)
Soil pHDecimal. The manual example is 5.5. Kerala soils are mostly acidic.
N, P, KDecimals. The manual examples are N 40, P 30, K 20.

The codes match the LabelEncoder order that the notebook used for training. Label encoders sort names alphabetically, but this list is not alphabetical, so the codes probably came from a hand-made mapping. The training notebook is not in the repo, so you cannot check this from the code.

Data flow

Form 7 number fields fetch POST /predict · JSON Pydantic types only, no ranges StandardScaler 1 × 7 array RF classifier · 100 trees class id → LabelEncoder → name RF regressor · 100 trees quantity in kg/acre JSON response fertilizer_name · fertilizer_amount_kg_acre page shows “name — amount kg/acre”
One scaled input feeds two models. The classifier picks the fertilizer. The regressor picks the dose. The two models do not share a result, so the dose does not depend on the predicted fertilizer.

What is in the pickle file

The file holds one Python dictionary with four keys. The API reads them at startup. The values below come from reading the pickle opcodes, not from loading the file.

KeyObjectDetails
clf_modelRandomForestClassifier100 trees, Gini criterion, no depth limit, max_features="sqrt", 7 input features. Fit on 400 rows.
reg_modelRandomForestRegressor100 trees, squared-error criterion. Predicts kg/acre.
scalerStandardScalerFit with column names Soil_Type, Crop_Type, Season, Soil_pH, Nitrogen, Phosphorus, Potassium.
target_encoderLabelEncoder6 classes: 18-18-18, 20-20-20, DAP, Factamfos, Potash, Urea.

Design points to explain

  • Why Random Forest. The data is small and tabular. A forest handles mixed categorical codes and numbers well. It needs little tuning and gives feature importances.
  • Why scale for trees. Trees do not need scaling. The scaler does no harm, but it is not necessary. Say this if someone asks.
  • Codes as numbers. Soil, crop and season are label-encoded integers. Trees can split on them, but the order of the codes has no meaning. One-hot encoding is a cleaner choice.
  • Feature-name warning. The scaler was fit on a DataFrame with column names. The API passes a plain NumPy array. scikit-learn writes a “X does not have valid feature names” warning on each call. The result is still correct.

6 · Disease detection

From a leaf photo to one of 51 classes.

Procedure: check a leaf

  1. Open /disease_detect.
  2. Take a clear photo of one leaf. Use daylight. Fill the frame with the leaf.
  3. Drag the photo onto the drop zone, or click the drop zone and select the file.
  4. Wait. The result box shows “Processing...”.
  5. Read the class name and the confidence, for example Rice_Brown spot (91.4%).
Note

The page accepts only files with an image/ type. For other files it shows an alert. The model always returns one of its 51 classes, also for a photo that is not a leaf. A low confidence value is a sign that the photo is out of scope.

Inference pipeline

Upload drop or click · image/* fetch POST FormData "file" PIL open convert("RGB") Resize 128 × 128, no crop Normalise ÷ 255 · add batch axis CNN 4 conv blocks Softmax 51 scores, sum = 1 argmax index of top score Look up name CLASS_NAMES[idx] JSON prediction, confidence
The resize does not keep the aspect ratio. A tall phone photo is squeezed into a square. Confidence is the top softmax score × 100, rounded to 2 decimals.

CNN layers (disease_cnn.h5)

Read from the model config inside the HDF5 file. Each convolution uses a 3 × 3 kernel, padding="same", then batch normalisation and ReLU.

BlockLayersOutput shape
InputRGB image128 × 128 × 3
1Conv 32 → BatchNorm → ReLU → MaxPool 2 × 264 × 64 × 32
2Conv 64 → BatchNorm → ReLU → MaxPool 2 × 232 × 32 × 64
3Conv 128 → BatchNorm → ReLU → MaxPool 2 × 216 × 16 × 128
4Conv 256 → BatchNorm → ReLU16 × 16 × 256
HeadGlobalAveragePooling2D256
HeadDense 128, ReLU → Dropout 0.6128
OutputDense 51, softmax51
  • About 430,000 parameters. This number is calculated from the layer sizes.
  • Training settings saved in the file: loss sparse_categorical_crossentropy, metric accuracy, Adam inside a LossScaleOptimizer, mixed_float16 policy. The saved learning rate is about 1.6 × 10⁻⁵. That low value suggests a learning-rate schedule that reduced it during training.
  • Global average pooling in place of Flatten keeps the head small and reduces overfitting. Dropout 0.6 is high, for the same reason.
  • backend/disease_cnn.keras is a second model with 5 conv blocks (up to 512 filters) and a 256-unit dense layer. It is an HDF5 file with a .keras name. No code loads it.

Classes by crop

CropCountClasses
Tomato10Bacterial spot, Early blight, Late blight, Leaf Mold, Septoria leaf spot, Spider mites, Target Spot, Yellow Leaf Curl Virus, Mosaic virus, healthy
Banana9Fruit-Scarring Beetle, Skipper Damage, Split Peel, Anthracnose, Black and Yellow Sigatoka, Panama Wilt, Chewing insect damage, Healthy Banana, Healthy Banana leaf
Rice9Hispa, Bacterial leaf blight, Brown spot, Leaf smut, Leaf Blast, Leaf scald, Narrow Brown Leaf Spot, Sheath Blight, Healthy Rice Leaf
Rubber6Birds-eye, Colleotrichum (spelt “Colleorichum”), Corynespora, Pesta, Powdery mildew, Healthy
Corn (maize)4Cercospora / Gray leaf spot, Common rust, Northern Leaf Blight, healthy
Coconut4Bud Root Dropping, Bud Rot, Gray Leaf Spot, Leaf Rot
Potato3Early blight, Late blight, healthy
Coffee3leaf rust, phoma, healthy (spelt “Health leaves”)
Bell pepper2Bacterial spot, healthy
No crop prefix1Stem Bleeding (a coconut disease)

9 of the 51 classes are “healthy” classes. Tomato, potato, corn and pepper names follow the public PlantVillage dataset. Banana, rice, rubber, coconut and coffee classes come from other datasets and add Kerala crops.

Caution

The order of CLASS_NAMES must be the same as the class order during training. Keras sorts training folders by name. If someone edits, sorts or renames this list, every prediction gets the wrong label and nothing fails.

7 · Kisan Mitra chatbot

Kisan Mitra means “farmer’s friend”.

Procedure: ask a question

  1. Open /chatbot.
  2. Type your question in the box, for example “Why are my paddy leaves turning yellow?”.
  3. Push Enter or click the send button.
  4. Read the answer in the grey bubble on the left.

How it works

  • The page adds your message to the list. Then it posts {"message": "..."} to /chat.
  • The API calls InferenceClient.chat_completion with model mistralai/Mistral-7B-Instruct-v0.2, max_tokens=150 and temperature=0.2.
  • The system prompt makes the bot an agriculture expert for Indian farmers. It asks for short, practical, plain-text answers with no markdown, no headings, no bold and no bullet symbols. If the question is vague, the bot must ask one clarifying question.
  • The API returns {"response": "..."}. The page adds it as a bot message.
  • The page sends only the latest message. The bot has no memory of earlier turns.
Warning · the bot does not see the question

ask_query(question) receives the question, but it sends only the system message to the LLM. The user message is never in the messages list. So the model answers the system prompt, not the farmer. Each reply is a generic greeting or a guess.

messages=[
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": question},   # this line is missing
],

Add the user message to fix it. To give the bot memory, send the last few turns from the page and add them to the list.

Why these settings

  • 150 tokens keeps answers to about 100 words. That fits the chat bubble and a phone screen. It can also cut an answer in the middle of a sentence.
  • Temperature 0.2 makes answers stable and less creative. That is correct for advice.
  • No markdown because the page shows the text as-is. It does not render markdown.
  • Hosted model because a 7B model does not fit in a free Render instance. The service only needs huggingface_hub, but its requirements file also installs TensorFlow 2.10.

8 · Weather, schemes, prices

The dashboard panel and the two static pages.

Weather card

  • The card calls the OpenWeatherMap /data/2.5/forecast endpoint for Kottayam, in metric units, when it mounts.
  • The forecast has one entry for each 3 hours. The card keeps the first entry of each date and shows 6 days: today large, then 5 small days.
  • The icon depends on weather[0].main: Clear = sun, Clouds = cloud, Rain = drops, Thunderstorm = storm. Other values show a cloud.
  • The first entry of “today” can be a night value. So the large temperature is not always the current temperature.
  • If the call fails, the card shows “Loading weather...” for ever. It writes the error only to the console.
Warning

The OpenWeatherMap API key is a constant in weatherCard.js. It is in the public repo and in the browser bundle. Rotate the key. Move the call to a Next.js API route that reads the key from an environment variable.

Government schemes

LevelSchemeLink
CentralPM Kisan Samman Nidhi (PM-KISAN)pmkisan.gov.in
CentralRashtriya Krishi Vikas Yojana (RKVY)rkvy.da.gov.in
CentralMission for Integrated Development of Horticulture (MIDH)midh.gov.in
StateScheme on Coconut Development (Kerala State Plan)keralaagriculture.gov.in (PDF)
StateKerala State Land Use Board – Jaivasamridhikoppam Kattakadakslub.kerala.gov.in

The links are hard-coded in dashboard.js. They open in a new tab.

Market prices

The page shows five fixed rows dated 2025-09-29: tomato, potato, onion, banana and basmati rice, in markets in Delhi and Bangalore. Each row has a trend arrow (up, down or stable). The feature card says “(under development)”. A real version can read daily mandi prices from the government Agmarknet data on data.gov.in.

Mission page

The mission is to give farmers accessible, reliable technology for crop health, fertilizer, soil quality and weather. The stated future plans are yield prediction, market price forecasting and climate-resilient farming.

9 · API tables

Every endpoint in the three services.

ServiceMethod · pathRequestResponse
FertilizerGET /—{"status": "Fertilizer API running"}
FertilizerPOST /predictJSON: soil_type, crop_type, season (int); soil_ph, nitrogen, phosphorus, potassium (float)fertilizer_name (string), fertilizer_amount_kg_acre (float, 2 dp)
DiseaseGET /—{"status": "Disease API running"}
DiseasePOST /predictmultipart/form-data, field fileprediction (class name), confidence (percent, 2 dp)
ChatbotGET /—{"status": "Chatbot API running"}
ChatbotPOST /chatJSON: message (string)response (string)
Next.jsPOST /api/predictmultipart, field fileForwards to localhost:8000/predict. Not used.

Errors

  • A wrong JSON type gives 422 from FastAPI and Pydantic.
  • A model that did not load gives 500 “Model not loaded”. In practice the service stops at startup first, because the startup hook raises RuntimeError.
  • Any other error gives 500 with str(e) as the detail. This shows internal messages to the client.

Example calls

curl -X POST https://fertilizer-api-5dhb.onrender.com/predict \
  -H "Content-Type: application/json" \
  -d '{"soil_type":0,"crop_type":0,"season":0,"soil_ph":4.8,
       "nitrogen":52,"phosphorus":42,"potassium":62}'
# {"fertilizer_name":"Urea","fertilizer_amount_kg_acre":...}
curl -X POST https://disease-api-osi8.onrender.com/predict \
  -F "file=@backend/sample-leaf.jpeg"
# {"prediction":"...","confidence":...}
curl -X POST https://chatbot-api-j4zf.onrender.com/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"Best time to apply potash to coconut?"}'
# {"response":"..."}

The fertilizer input above is the one row in history_kerala.csv. That row records Urea, 170.03 kg/acre for laterite soil, paddy, Virippu.

Note

FastAPI also serves interactive docs at /docs on each service. Use them to test the APIs without the front end.

10 · Config and deploy

Environment variables, versions and hosts.

Environment variables

NameWhereUse
NEXT_PUBLIC_FERTILIZER_APIVercelBase URL of the fertilizer service.
NEXT_PUBLIC_DISEASE_APIVercelBase URL of the disease service.
NEXT_PUBLIC_CHATBOT_APIVercelBase URL of the chatbot service.
HF_TOKENRender (chatbot)Hugging Face access token. Required. The service stops at import time without it.
PORTRenderSet by Render. Used in the start command.
Note

Next.js puts NEXT_PUBLIC_* values into the bundle at build time. If you change a URL in Vercel, build and deploy again.

Versions

ServicePythonKey pins
Fertilizer3.13.1numpy 2.2.2, pandas 2.2.3, scikit-learn 1.6.1
Disease3.10.0tensorflow 2.10.0, keras 2.10.0, numpy 1.23.5, pandas 2.3.2
Chatbot3.10.0 (3.10.13 in render.yaml)huggingface_hub (not pinned), plus TensorFlow 2.10, which it does not use
Front endNodenext ^16.1.6, react ^19.2.4, tailwindcss ^3.4.17, eslint-config-next 15.5.3

Pin scikit-learn exactly. A pickle made with one scikit-learn version can fail or warn when another version loads it. That is why the fertilizer service has its own environment.

Hosts

  • Vercel builds the Next.js app from the repo root.
  • Render runs each API as a web service with uvicorn <module>:app --host 0.0.0.0 --port $PORT.
  • render.yaml defines only chatbot-api, with rootDir: backend/chatbot_api. Its start command is uvicorn main:app, but the file is chatbot_api.py. The live services were probably made in the Render dashboard, not from this file.
Caution

Render free services sleep after 15 minutes with no traffic. The first request then waits while the service starts and loads its model. TensorFlow makes the disease service the slowest. On 30 Oct 2026 none of the three APIs answered within 25 seconds. The Vercel site answered at once. Tell the user about the delay, or keep the services awake.

11 · Run it locally

Four terminals: three APIs and the web app.

Caution

Use Python 3.10 for the disease and chatbot services. TensorFlow 2.10 does not install on Python 3.11 or newer. On Apple silicon, TensorFlow 2.10 needs tensorflow-macos or Rosetta.

  1. Clone the repo.
    git clone https://github.com/MeSanskarSingh/krishi-ai
    cd krishi-ai
  2. Start the fertilizer API on port 8001.
    cd backend/fertilizer_api
    python3.13 -m venv .venv && source .venv/bin/activate
    pip install -r requirements.txt
    uvicorn fertilizer_api:app --port 8001
  3. In a new terminal, start the disease API on port 8002.
    cd backend/disease_api
    python3.10 -m venv .venv && source .venv/bin/activate
    pip install -r requirements.txt
    uvicorn disease_api:app --port 8002
  4. In a new terminal, set your Hugging Face token and start the chatbot API on port 8003.
    cd backend/chatbot_api
    python3.10 -m venv .venv && source .venv/bin/activate
    pip install -r requirements.txt
    export HF_TOKEN=hf_your_token
    uvicorn chatbot_api:app --port 8003
  5. In the repo root, make a file named .env.local with the three URLs.
    NEXT_PUBLIC_FERTILIZER_API=http://localhost:8001
    NEXT_PUBLIC_DISEASE_API=http://localhost:8002
    NEXT_PUBLIC_CHATBOT_API=http://localhost:8003
  6. Install the packages and start the web app.
    npm install
    npm run dev
  7. Open http://localhost:3000. The CORS lists allow this origin.
  8. Do a check of each API. Open http://localhost:8001/docs, :8002/docs and :8003/docs.
Note

Use port 3000 for the web app. If Next.js moves to port 3001, the APIs block the calls, because CORS allows only port 3000.

12 · README vs code

The code is the truth. Know these differences before someone else finds them.

TopicREADME saysCode does
Maturity“Production-ready”No tests, no input ranges, no error messages in the UI, a chatbot that drops the question, and an API key in the client.
Chatbot“Context-aware agricultural assistant”No conversation history. The user question is not sent to the model at all.
ArchitectureBrowser → Next.js → APIsThe browser calls the APIs directly. The Next.js server is not in the path. The one API route points at localhost:8000 and is unused.
Encoded inputs“Encoded categorical agricultural inputs”True, but the farmer types the integer codes by hand from a pop-up table.
Disease scope“Detects crop diseases”51 classes on 10 crops. Some classes are pest or fruit damage, and 9 are healthy classes.
DeployThree APIs on Renderrender.yaml has one service, and its start command names the wrong module.
Version pins“Version-pinned ML environments”Partly true. The chatbot leaves huggingface_hub unpinned and pins TensorFlow, which it does not use.
LibrariesNext.js, TailwindAlso MUI and Emotion for icons. chart.js, react-chartjs-2 and node-fetch are installed and not used.
LanguagesNot statedi18n packages and a Malayalam file exist. The app shows English only.
FeaturesThree AI featuresAlso a weather card, scheme links, a static price table and a mission page.
ModelsOne CNNTwo CNN files. The 20 MB backend/disease_cnn.keras is a larger model that no code loads.

13 · Risks and bugs

Found by reading the code. Rated by how much harm they can do.

Warning · fix before real users

Items 1 and 2 break a feature or leak a secret. If you present this project, say you know about them and say how you will fix them. That shows good judgement.

#LevelProblemFix
1highThe chatbot sends only the system prompt. The farmer’s question never reaches the LLM.Add {"role": "user", "content": question} to messages.
2highThe OpenWeatherMap API key is hard-coded in weatherCard.js and public on GitHub.Rotate the key. Read it from an env var in a server-side API route.
3mediumNo range checks. Soil code 9, crop code −1 or pH 40 still give a confident answer.Use Field(ge=…, le=…) in Pydantic. Use drop-down menus with names in the UI.
4mediumSilent failures. The fertilizer page hides errors. The chatbot has no try/catch, so a failed call shows an empty bubble. The weather card stays on “Loading”.Show an error message and a retry button. Show “service is starting” on a cold start.
5mediumThe disease API reads any upload fully into memory. It has no size limit and no type check on the server.Limit the size (for example 5 MB). Check the content type. Set PIL’s pixel limit.
6mediumError responses return str(e). This shows internal details.Log the error. Return a short fixed message.
7mediumpickle.load can run code. A changed model file can take over the server.Load only trusted files. Check a hash. Consider skops or ONNX.
8mediumrender.yaml starts main:app, which does not exist.Use chatbot_api:app. Add the other two services to the file.
9mediumNo tests in any service or in the front end.Add pytest with FastAPI TestClient for each endpoint, and a fixed-input model check.
10lowComponents and CSS modules are inside src/pages/, so they become routes.Move them to src/components/ and src/styles/.
11lowThe home page renders the navbar twice: once in _app.js, once in index.js.Remove it from index.js.
12lowThe footer “About Us” link goes to /about. That page does not exist.Link to /mission or add the page.
13lowi18n is set up but not connected. The two locale files have different keys.Add serverSideTranslations and use t(). Make the keys the same.
14lowClass names are raw and not consistent (“Healthy Banana leaf” with two spaces, “coffee_Health leaves”, underscores).Map each class to a clean display name and an advice text.
15lowDead code and files: /api/predict, the grayscale branch in preprocess, the unused .keras model, __pycache__ with no sources.Delete them.
16lowAbout 31 MB of model files are in normal Git.Use Git LFS or download the models at build time.
17low@app.on_event("startup") is deprecated in FastAPI.Use a lifespan handler.
18loweslint-config-next is 15.5.3, but Next.js is 16.Use the same major version.

14 · Interview Q&A

Say the answers in your own words. Answers use “I” and “we”. Change them to match the part you built.

Explain Krishi AI in 30 seconds.basics

Krishi AI is a web assistant for farmers in Kerala. It has three AI features. A Random Forest model recommends a fertilizer and a dose from soil data. A CNN finds disease in a leaf photo. A chatbot built on Mistral 7B answers farm questions. The front end is Next.js on Vercel. Each AI feature is its own FastAPI service on Render.

Who is the user, and what problem do you solve?basics

The user is a small farmer or an extension worker in Kerala. Soil test cards give N, P and K numbers, but many farmers do not know what to buy or how much. Disease is often found late, and an expert visit takes days. We give a first answer in seconds. The app is a guide, not a replacement for an agriculture officer.

Walk me through a fertilizer request.basics · ml
  • The farmer enters 3 codes and 4 numbers in the form.
  • The page posts JSON to /predict on the fertilizer service.
  • Pydantic checks the types. The API builds a 1 × 7 NumPy array and scales it with the saved StandardScaler.
  • The classifier gives a class id. The LabelEncoder turns it into a name, for example Urea.
  • The regressor gives the dose in kg/acre. The API rounds it to 2 decimals and returns both values.
Walk me through a disease check.basics · ml
  • The user drops a photo. The page shows a preview and posts the file as multipart form data.
  • The API opens it with PIL, converts it to RGB, resizes it to 128 × 128 and divides by 255.
  • The CNN returns 51 softmax scores. We take the highest score and look up its name.
  • We return the name and the score as a percent. The page shows “name (score%)”.
Why Random Forest for fertilizer and not a neural network?ml

The data is small and tabular: 7 columns and a few hundred rows. Forests work well on this kind of data with little tuning. They do not overfit as fast as a deep model on a small set. They also give feature importances, so we can explain which input mattered. A neural network needs more data and gives no clear benefit here.

Why two models, a classifier and a regressor?ml

The output has two parts. The fertilizer name is a category, so it is a classification task. The dose is a number, so it is a regression task. Two models keep each task simple.

The weak point: the two models are independent. The regressor does not know which fertilizer the classifier picked. A better design gives the predicted class to the regressor as an input, or trains one regressor for each fertilizer.

Do Random Forests need feature scaling?ml

No. A tree splits on thresholds, and scaling does not change the order of values. So the scaler has no effect on the result. We kept it from the training pipeline. If I clean this up, I will remove it, or I will keep it only if we later try a model that needs it, such as logistic regression.

How did you encode soil, crop and season? What is the problem with that?ml

Each is an integer code, for example Laterite = 0, Red Soil = 4. Trees can split on these numbers. But the numbers suggest an order that does not exist. Red Soil is not “more” than Laterite. One-hot encoding removes that false order. For trees the effect is small, but one-hot is the cleaner choice.

The bigger issue is the UI. The farmer must type the codes. A drop-down with names fixes this and also removes invalid codes.

Describe the CNN.ml

Input is 128 × 128 × 3. There are four convolution blocks with 32, 64, 128 and 256 filters. Each block is a 3 × 3 convolution, batch normalisation and ReLU. The first three blocks end with 2 × 2 max pooling. Then global average pooling, a 128-unit dense layer, dropout 0.6 and a 51-way softmax. It has about 430,000 parameters, so it is fast on a CPU.

Why global average pooling and dropout 0.6?ml

Flatten on a 16 × 16 × 256 map gives 65,536 values. A dense layer on that has millions of weights and overfits. Global average pooling gives 256 values, one per filter. That keeps the head small. Dropout 0.6 adds more regularisation, because some classes have few images.

Why not transfer learning, for example MobileNet or EfficientNet?ml

A small custom CNN is easy to train and small to deploy. But a pretrained backbone usually gives better accuracy on real field photos, because it has already learned general shapes and textures. MobileNetV2 or EfficientNet-B0 is still small enough for a CPU. That is the first change I would test, with the same 51 classes.

The model always answers. How do you handle a photo that is not a leaf?ml

Softmax always picks one of the 51 classes, also for a photo of a cow. Today we only show the confidence. I would add a threshold. Below, for example, 60%, the app says “not sure, take a clearer photo”. A stronger fix is an out-of-distribution check, or a first model that answers “is this a leaf of a supported crop?”.

What is mixed_float16 and why was it used?ml

Mixed precision does most maths in 16-bit floats and keeps the weights in 32-bit. On a modern GPU this trains faster and uses less memory. The loss-scale optimizer multiplies the loss so small gradients do not round to zero in 16-bit. At inference on a CPU it gives no speed-up, but it does no harm.

How do you know the models are accurate?ml · story

Be honest here. The repo has no training notebook, no test split results and no metrics file. The CNN file only records that accuracy was the training metric. Say what you measured during training, for example validation accuracy, and on which data. Then say what you would add: a held-out test set, a confusion matrix per crop, and a small set of real field photos from Kerala, because lab photos are cleaner than field photos.

Why does the order of CLASS_NAMES matter?ml

The model outputs index numbers, not names. Index 20 means whatever class was index 20 during training. Keras assigns indices by sorting the training folder names. If the list in the API has a different order, every label is wrong and nothing fails. A safer design saves the class list next to the model file and loads it from there.

How does the chatbot work?llm

The page posts the message to /chat. The API calls the Hugging Face Inference API with Mistral-7B-Instruct v0.2, a system prompt, 150 max tokens and temperature 0.2. It returns the text.

Then say the bug before they find it: the code does not add the user message to the list, so the model never sees the question. The fix is one line.

Why Mistral 7B through Hugging Face, and not GPT or a local model?llm

Mistral 7B Instruct is open, cheap, and good enough for short advice. The Inference API means we do not host a GPU. A free Render instance cannot run a 7B model. The trade-offs are rate limits, a dependency on a third party, and weaker Malayalam support than larger models.

How would you make the chatbot answers more reliable?llm
  • Send the user message and the last few turns.
  • Add retrieval (RAG) over trusted sources, such as the Kerala Agricultural University Package of Practices. Make the bot answer from that text.
  • Pass context from the other features, for example the last disease result.
  • Add a refusal rule for pesticide doses it cannot support with a source.
  • Log questions (without personal data) and review bad answers.
Why does the prompt forbid markdown?llm · front

The chat bubble shows the text as-is. Markdown symbols like ** and # look like noise to a farmer. Plain text is also easier for a future text-to-speech feature.

Why three separate services?backend · ops

The models need different stacks. TensorFlow 2.10 needs Python 3.10. The fertilizer pickle needs scikit-learn 1.6.1 and NumPy 2. Separate services keep these environments apart. A crash or a slow start in one model does not stop the others. We can also scale the heavy disease service alone.

The cost is more deploys, more cold starts and three CORS lists. One Python process cannot load both stacks, so the split is a fair choice here.

Why load the models at startup?backend

Loading a model takes seconds. If we load it per request, every request is slow. We load it once into a global variable, and each request only runs inference. If loading fails, the startup hook raises an error and the service does not start. That is better than a service that runs but answers 500 to every call.

The disease endpoint is async but calls model.predict. Is that a problem?backend

Yes. model.predict is blocking CPU work. In an async def handler it blocks the event loop, so other requests wait. Use a plain def handler, so FastAPI runs it in a thread pool, or call the model with run_in_threadpool. For one image at a time this is a small issue. Under load it matters.

How would you add input validation to the fertilizer API?backend
class FertilizerInput(BaseModel):
    soil_type: int = Field(ge=0, le=4)
    crop_type: int = Field(ge=0, le=6)
    season:    int = Field(ge=0, le=2)
    soil_ph:   float = Field(ge=3, le=10)
    nitrogen:  float = Field(ge=0)
    ...

FastAPI then returns 422 with a clear message. Better still, accept names (“Laterite Soil”) as an Enum and map them to codes on the server.

Why does the browser call the APIs directly?front · backend

It is simple. There is no extra hop, and Vercel functions do not need to stream images. The cost: the API URLs are public, each API needs CORS, and we cannot hide keys. The weather key shows the risk. A Next.js API route as a proxy fixes the key problem and gives one place for rate limits.

What would you improve in the UI?front
  • Drop-downs with names for soil, crop and season.
  • Loading and error states on every page, with a “server is starting” message.
  • Clean disease names with short treatment advice.
  • Malayalam text by connecting next-i18next.
  • A mobile layout. Farmers use phones. Some pages use fixed widths and a fixed sidebar.
  • Camera capture on the disease page.
Why Pages Router and not App Router?front

The pages are simple client pages, and next-i18next targets the Pages Router. So the Pages Router was a fast choice. With the App Router we could use server components for the static pages and server actions as a proxy to the APIs.

What security problems does the project have?sec
  • A weather API key is in the client code and on GitHub.
  • The APIs have no auth and no rate limit. CORS only stops other websites in a browser. It does not stop curl or a script.
  • No upload size limit on the disease API.
  • pickle.load can run code from the file.
  • Errors return internal messages.

The HF token is handled correctly. It is an environment variable on the server.

The demo is slow on the first request. Why?ops

Render free services sleep after 15 minutes. The first request wakes the service. The service must start Python and load the model before it answers. TensorFlow takes the longest. Fixes: a paid instance, a scheduled ping, or a warm-up call when the dashboard opens and a “starting” message in the UI.

How would you scale this to many users?ops
  • Keep the services stateless and run more instances behind the Render load balancer.
  • Convert the CNN to TensorFlow Lite or ONNX for faster CPU inference and a smaller image.
  • Batch disease requests if load is high.
  • Cache fertilizer answers. The same 7 inputs always give the same answer.
  • Add rate limits and a queue in front of the LLM, because the Inference API has quotas.
How would you test this project?ops · backend
  • pytest with FastAPI TestClient for each endpoint: health, a valid request, a 422 case.
  • A model check: a fixed input gives a known output, so a model file change is visible.
  • A check that len(CLASS_NAMES) equals the model output size.
  • For the chatbot, mock InferenceClient and assert that the user message is in messages. That test finds the current bug.
  • Playwright for the three main user flows in the browser.
What was the hardest part?story

A good answer from the repo: version conflicts. TensorFlow 2.10 needs Python 3.10 and old NumPy. The fertilizer pickle needs new scikit-learn and NumPy 2. One environment could not hold both. We split the services and pinned each one. Pick the part you personally did and give a concrete detail.

What would you do next?story
  1. Fix the chatbot prompt bug and move the weather key to the server.
  2. Add input ranges and drop-downs to the fertilizer form.
  3. Add tests and a held-out evaluation for both models.
  4. Try a pretrained backbone for the CNN and a confidence threshold.
  5. Connect Malayalam and make the layout work on phones.
  6. Use real market prices from Agmarknet.

15 · Numbers to know

Short facts for fast recall.

ItemValue
Services1 web app + 3 FastAPI APIs
Fertilizer inputs7 (3 codes + pH + N, P, K)
Soils · crops · seasons5 · 7 · 3
Fertilizer classes6 (18-18-18, 20-20-20, DAP, Factamfos, Potash, Urea)
Trees per forest100 (classifier and regressor)
Fertilizer training rows400 (from the pickle)
CNN input128 × 128 × 3, values 0 to 1
CNN filters32 → 64 → 128 → 256
CNN headGAP → Dense 128 → Dropout 0.6 → Dense 51
CNN parametersabout 430,000
Disease classes51 on 10 crops, 9 healthy
ChatbotMistral-7B-Instruct v0.2 · 150 tokens · temperature 0.2
WeatherKottayam · 3-hour forecast · 6 days shown
Roundingkg/acre and confidence % to 2 decimals
Model files.pkl 6.2 MB · .h5 5.0 MB · unused .keras 20 MB
Python3.13.1 (fertilizer) · 3.10 (disease, chatbot)
CORS originslocalhost:3000 and krishi-ai-sigma.vercel.app
Localesen, ml (ml not active)

16 · Glossary

Terms in simple words.

Krishi
Farming or agriculture (Hindi).
Kisan Mitra
“Farmer’s friend”. The name of the chatbot.
Virippu
The first paddy season in Kerala, April–May to August–September. Same time as Kharif.
Mundakan
The second paddy season, September–October to December–January. The app maps it to Rabi.
Puncha
The summer season, December–January to March–April.
N, P, K
Nitrogen, phosphorus and potassium. The three main plant nutrients in a soil test.
Soil pH
How acidic the soil is. Below 7 is acidic. Laterite soils in Kerala are often near pH 5.
Laterite
A red, iron-rich, acidic soil. The most common soil in Kerala.
DAP
Diammonium phosphate. A fertilizer with nitrogen and phosphorus.
Factamfos
A complex NP fertilizer made by FACT, a company in Kerala.
18-18-18
An NPK fertilizer with 18% of each nutrient. 20-20-20 is the same idea.
kg/acre
Kilograms per acre. 1 acre is about 0.405 hectare.
Random Forest
Many decision trees trained on random samples. The forest votes (classifier) or averages (regressor).
StandardScaler
Changes each column to mean 0 and standard deviation 1.
LabelEncoder
Maps each class name to an integer and back.
Pickle
Python’s format to save objects to a file. Loading it can run code.
CNN
Convolutional neural network. A model that learns image patterns with small filters.
Batch normalisation
A layer that keeps activations at a stable scale, so training is faster.
Global average pooling
Takes the mean of each feature map. Turns 16 × 16 × 256 into 256 numbers.
Dropout
Turns off a random part of the units during training to reduce overfitting.
Softmax
Turns scores into probabilities that add up to 1.
argmax
The position of the largest value.
Mixed precision
Training with 16-bit and 32-bit numbers together to go faster.
LLM
Large language model. A program that writes text, for example Mistral 7B.
System prompt
Fixed instructions that tell an LLM its role and rules.
Temperature
How random the LLM output is. Low values give stable answers.
FastAPI
A Python web framework. It checks request data with Pydantic.
Uvicorn
The server program that runs a FastAPI app.
CORS
Browser rules that control which websites can call an API.
Cold start
The delay when a sleeping service must start before it can answer.
i18n
Internationalisation. Support for more than one language.
PlantVillage
A public dataset of leaf photos with labels for crop diseases.

17 · About this guide

The writing rules, and what the guide is based on.

This guide uses ASD-STE100 Simplified Technical English. STE is a controlled language for maintenance manuals. It makes text easy to read for people who do not speak English as a first language.

STE ruleHow this guide uses it
Short sentencesProcedure sentences have 20 words or fewer. Descriptive sentences have 25 words or fewer.
One instruction per stepEach numbered step gives one action. Commands use the imperative (“Open”, “Type”, “Click”).
Active voiceThe text says who does the action: “The API returns…”, not “…is returned”.
One word, one meaning“Service” always means one FastAPI program. “Class” always means one model output label.
Safety noticesWarning = risk of harm, data loss or a broken feature. Caution = risk of a wrong result. Note = useful information.
Technical namesCode names, paths and values are in monospace and are not changed.

Facts come from the source code at commit c441c6d. Model details come from the model configs in the .h5 file and from the pickle opcodes. The pickle was read with pickletools and not loaded. Live endpoints were checked on 30 Oct 2026.