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.
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.”
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.
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.
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
| Program | Folder | Job |
|---|---|---|
| Web app | src/, 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 API | backend/fertilizer_api/ | Loads the pickle file at startup. Scales the 7 inputs. Returns the fertilizer name and the quantity in kg/acre. |
| Disease API | backend/disease_api/ | Loads the CNN at startup. Resizes the image to 128 × 128. Returns the top class and its confidence in percent. |
| Chatbot API | backend/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.
| Path | What it contains |
|---|---|
src/pages/index.js | Landing page. Renders the navbar and the hero section. |
src/pages/dashboard.js | Weather card, scheme links and the four feature cards. |
src/pages/fertilizer.js | Fertilizer form, result box and the “Reference Manual” pop-up with the input codes. |
src/pages/disease_detect.js | Drag-and-drop image upload and the result text box. |
src/pages/chatbot.js | Chat window for Kisan Mitra. |
src/pages/market_prices.js | Table of five fixed demo prices. |
src/pages/mission.js | Mission text on a background image. |
src/pages/_app.js | Wraps every page with the navbar and appWithTranslation. |
src/pages/_document.js | Adds 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.js | Next.js API route that forwards an image to localhost:8000. No page uses it. |
src/data/cardsData.js | Title, icon, text and link for each feature card. |
src/styles/globals.css | Tailwind 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.yaml | Render blueprint for the chatbot service only. |
next-i18next.config.js | Locales en and ml. Default en. |
tailwind.config.js | Colour greenfooter #3F9148, background #FCF9F6, three font families. |
4 · Frontend pages
Next.js Pages Router. All data calls run in the browser.
Routes
| Route | Page title | What the user sees | Calls |
|---|---|---|---|
/ | Krishi AI - Smart Farming Assistant | Hero text “Your Digital Krishi Sathi”, a farmer picture, the “LET’S GET STARTED” button, and a green footer with a caution line. | — |
/dashboard | Krishi AI Dashboard | Left panel: weather card and scheme links. Main area: four feature cards. | OpenWeatherMap |
/fertilizer | Fertilizer Prediction | Seven number fields, a submit button, and a pop-up with the code table. | Fertilizer API |
/disease_detect | Crop Disease Detection | Drop zone, image preview and a read-only result box. | Disease API |
/chatbot | Kisan Mitra Chatbot | Message list and an input with a send button. Enter also sends. | Chatbot API |
/market_prices | Market Price Prediction | Five rows: crop, variety, market, unit, price in ₹, trend arrow. | — |
/mission | Our Mission at Krishi AI | Three paragraphs about the mission and future plans. | — |
Shared layout
_app.jsrenders<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.pathnamefor 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 aLinkwith 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.
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
- Open
/fertilizer. - Click View Reference Manual. Find the codes for your soil, crop and season.
- Type the soil code (0 to 4), the crop code (0 to 6) and the season code (0 to 2).
- Type the soil pH. Type the nitrogen (N), phosphorus (P) and potassium (K) values from your soil test.
- Click Get Recommendation. The button shows “Predicting...” while it waits.
- Read the result in the green box, for example Urea — 170.03 kg/acre.
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
| Field | Code → value |
|---|---|
| Soil type | 0 Laterite Soil · 1 Coastal Alluvium · 2 Sandy Loam · 3 Forest Loam · 4 Red Soil |
| Crop type | 0 Rice (Paddy) · 1 Coconut · 2 Rubber · 3 Pepper · 4 Cardamom · 5 Cashew · 6 Banana |
| Season | 0 Virippu (Kharif) · 1 Mundakan (Rabi) · 2 Puncha (Summer) |
| Soil pH | Decimal. The manual example is 5.5. Kerala soils are mostly acidic. |
| N, P, K | Decimals. 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
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.
| Key | Object | Details |
|---|---|---|
clf_model | RandomForestClassifier | 100 trees, Gini criterion, no depth limit, max_features="sqrt", 7 input features. Fit on 400 rows. |
reg_model | RandomForestRegressor | 100 trees, squared-error criterion. Predicts kg/acre. |
scaler | StandardScaler | Fit with column names Soil_Type, Crop_Type, Season, Soil_pH, Nitrogen, Phosphorus, Potassium. |
target_encoder | LabelEncoder | 6 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
- Open
/disease_detect. - Take a clear photo of one leaf. Use daylight. Fill the frame with the leaf.
- Drag the photo onto the drop zone, or click the drop zone and select the file.
- Wait. The result box shows “Processing...”.
- Read the class name and the confidence, for example Rice_Brown spot (91.4%).
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
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.
| Block | Layers | Output shape |
|---|---|---|
| Input | RGB image | 128 × 128 × 3 |
| 1 | Conv 32 → BatchNorm → ReLU → MaxPool 2 × 2 | 64 × 64 × 32 |
| 2 | Conv 64 → BatchNorm → ReLU → MaxPool 2 × 2 | 32 × 32 × 64 |
| 3 | Conv 128 → BatchNorm → ReLU → MaxPool 2 × 2 | 16 × 16 × 128 |
| 4 | Conv 256 → BatchNorm → ReLU | 16 × 16 × 256 |
| Head | GlobalAveragePooling2D | 256 |
| Head | Dense 128, ReLU → Dropout 0.6 | 128 |
| Output | Dense 51, softmax | 51 |
- 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 aLossScaleOptimizer,mixed_float16policy. 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
Flattenkeeps the head small and reduces overfitting. Dropout 0.6 is high, for the same reason. backend/disease_cnn.kerasis a second model with 5 conv blocks (up to 512 filters) and a 256-unit dense layer. It is an HDF5 file with a.kerasname. No code loads it.
Classes by crop
| Crop | Count | Classes |
|---|---|---|
| Tomato | 10 | Bacterial spot, Early blight, Late blight, Leaf Mold, Septoria leaf spot, Spider mites, Target Spot, Yellow Leaf Curl Virus, Mosaic virus, healthy |
| Banana | 9 | Fruit-Scarring Beetle, Skipper Damage, Split Peel, Anthracnose, Black and Yellow Sigatoka, Panama Wilt, Chewing insect damage, Healthy Banana, Healthy Banana leaf |
| Rice | 9 | Hispa, Bacterial leaf blight, Brown spot, Leaf smut, Leaf Blast, Leaf scald, Narrow Brown Leaf Spot, Sheath Blight, Healthy Rice Leaf |
| Rubber | 6 | Birds-eye, Colleotrichum (spelt “Colleorichum”), Corynespora, Pesta, Powdery mildew, Healthy |
| Corn (maize) | 4 | Cercospora / Gray leaf spot, Common rust, Northern Leaf Blight, healthy |
| Coconut | 4 | Bud Root Dropping, Bud Rot, Gray Leaf Spot, Leaf Rot |
| Potato | 3 | Early blight, Late blight, healthy |
| Coffee | 3 | leaf rust, phoma, healthy (spelt “Health leaves”) |
| Bell pepper | 2 | Bacterial spot, healthy |
| No crop prefix | 1 | Stem 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.
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
- Open
/chatbot. - Type your question in the box, for example “Why are my paddy leaves turning yellow?”.
- Push Enter or click the send button.
- 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_completionwith modelmistralai/Mistral-7B-Instruct-v0.2,max_tokens=150andtemperature=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.
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/forecastendpoint 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.
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
| Level | Scheme | Link |
|---|---|---|
| Central | PM Kisan Samman Nidhi (PM-KISAN) | pmkisan.gov.in |
| Central | Rashtriya Krishi Vikas Yojana (RKVY) | rkvy.da.gov.in |
| Central | Mission for Integrated Development of Horticulture (MIDH) | midh.gov.in |
| State | Scheme on Coconut Development (Kerala State Plan) | keralaagriculture.gov.in (PDF) |
| State | Kerala State Land Use Board – Jaivasamridhikoppam Kattakada | kslub.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.
| Service | Method · path | Request | Response |
|---|---|---|---|
| Fertilizer | GET / | — | {"status": "Fertilizer API running"} |
| Fertilizer | POST /predict | JSON: soil_type, crop_type, season (int); soil_ph, nitrogen, phosphorus, potassium (float) | fertilizer_name (string), fertilizer_amount_kg_acre (float, 2 dp) |
| Disease | GET / | — | {"status": "Disease API running"} |
| Disease | POST /predict | multipart/form-data, field file | prediction (class name), confidence (percent, 2 dp) |
| Chatbot | GET / | — | {"status": "Chatbot API running"} |
| Chatbot | POST /chat | JSON: message (string) | response (string) |
| Next.js | POST /api/predict | multipart, field file | Forwards 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.
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
| Name | Where | Use |
|---|---|---|
NEXT_PUBLIC_FERTILIZER_API | Vercel | Base URL of the fertilizer service. |
NEXT_PUBLIC_DISEASE_API | Vercel | Base URL of the disease service. |
NEXT_PUBLIC_CHATBOT_API | Vercel | Base URL of the chatbot service. |
HF_TOKEN | Render (chatbot) | Hugging Face access token. Required. The service stops at import time without it. |
PORT | Render | Set by Render. Used in the start command. |
Next.js puts NEXT_PUBLIC_* values into the bundle at build time. If you change a URL in Vercel, build and deploy again.
Versions
| Service | Python | Key pins |
|---|---|---|
| Fertilizer | 3.13.1 | numpy 2.2.2, pandas 2.2.3, scikit-learn 1.6.1 |
| Disease | 3.10.0 | tensorflow 2.10.0, keras 2.10.0, numpy 1.23.5, pandas 2.3.2 |
| Chatbot | 3.10.0 (3.10.13 in render.yaml) | huggingface_hub (not pinned), plus TensorFlow 2.10, which it does not use |
| Front end | Node | next ^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.yamldefines onlychatbot-api, withrootDir: backend/chatbot_api. Its start command isuvicorn main:app, but the file ischatbot_api.py. The live services were probably made in the Render dashboard, not from this file.
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.
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.
- Clone the repo.
git clone https://github.com/MeSanskarSingh/krishi-ai cd krishi-ai - 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 - 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 - 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 - In the repo root, make a file named
.env.localwith the three URLs.NEXT_PUBLIC_FERTILIZER_API=http://localhost:8001 NEXT_PUBLIC_DISEASE_API=http://localhost:8002 NEXT_PUBLIC_CHATBOT_API=http://localhost:8003 - Install the packages and start the web app.
npm install npm run dev - Open
http://localhost:3000. The CORS lists allow this origin. - Do a check of each API. Open
http://localhost:8001/docs,:8002/docsand:8003/docs.
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.
| Topic | README says | Code 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. |
| Architecture | Browser → Next.js → APIs | The 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. |
| Deploy | Three APIs on Render | render.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. |
| Libraries | Next.js, Tailwind | Also MUI and Emotion for icons. chart.js, react-chartjs-2 and node-fetch are installed and not used. |
| Languages | Not stated | i18n packages and a Malayalam file exist. The app shows English only. |
| Features | Three AI features | Also a weather card, scheme links, a static price table and a mission page. |
| Models | One CNN | Two 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.
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.
| # | Level | Problem | Fix |
|---|---|---|---|
| 1 | high | The chatbot sends only the system prompt. The farmer’s question never reaches the LLM. | Add {"role": "user", "content": question} to messages. |
| 2 | high | The 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. |
| 3 | medium | No 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. |
| 4 | medium | Silent 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. |
| 5 | medium | The 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. |
| 6 | medium | Error responses return str(e). This shows internal details. | Log the error. Return a short fixed message. |
| 7 | medium | pickle.load can run code. A changed model file can take over the server. | Load only trusted files. Check a hash. Consider skops or ONNX. |
| 8 | medium | render.yaml starts main:app, which does not exist. | Use chatbot_api:app. Add the other two services to the file. |
| 9 | medium | No tests in any service or in the front end. | Add pytest with FastAPI TestClient for each endpoint, and a fixed-input model check. |
| 10 | low | Components and CSS modules are inside src/pages/, so they become routes. | Move them to src/components/ and src/styles/. |
| 11 | low | The home page renders the navbar twice: once in _app.js, once in index.js. | Remove it from index.js. |
| 12 | low | The footer “About Us” link goes to /about. That page does not exist. | Link to /mission or add the page. |
| 13 | low | i18n is set up but not connected. The two locale files have different keys. | Add serverSideTranslations and use t(). Make the keys the same. |
| 14 | low | Class 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. |
| 15 | low | Dead code and files: /api/predict, the grayscale branch in preprocess, the unused .keras model, __pycache__ with no sources. | Delete them. |
| 16 | low | About 31 MB of model files are in normal Git. | Use Git LFS or download the models at build time. |
| 17 | low | @app.on_event("startup") is deprecated in FastAPI. | Use a lifespan handler. |
| 18 | low | eslint-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
/predicton 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
LabelEncoderturns 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.loadcan 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
TestClientfor 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
InferenceClientand assert that the user message is inmessages. 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
- Fix the chatbot prompt bug and move the weather key to the server.
- Add input ranges and drop-downs to the fertilizer form.
- Add tests and a held-out evaluation for both models.
- Try a pretrained backbone for the CNN and a confidence threshold.
- Connect Malayalam and make the layout work on phones.
- Use real market prices from Agmarknet.
15 · Numbers to know
Short facts for fast recall.
| Item | Value |
|---|---|
| Services | 1 web app + 3 FastAPI APIs |
| Fertilizer inputs | 7 (3 codes + pH + N, P, K) |
| Soils · crops · seasons | 5 · 7 · 3 |
| Fertilizer classes | 6 (18-18-18, 20-20-20, DAP, Factamfos, Potash, Urea) |
| Trees per forest | 100 (classifier and regressor) |
| Fertilizer training rows | 400 (from the pickle) |
| CNN input | 128 × 128 × 3, values 0 to 1 |
| CNN filters | 32 → 64 → 128 → 256 |
| CNN head | GAP → Dense 128 → Dropout 0.6 → Dense 51 |
| CNN parameters | about 430,000 |
| Disease classes | 51 on 10 crops, 9 healthy |
| Chatbot | Mistral-7B-Instruct v0.2 · 150 tokens · temperature 0.2 |
| Weather | Kottayam · 3-hour forecast · 6 days shown |
| Rounding | kg/acre and confidence % to 2 decimals |
| Model files | .pkl 6.2 MB · .h5 5.0 MB · unused .keras 20 MB |
| Python | 3.13.1 (fertilizer) · 3.10 (disease, chatbot) |
| CORS origins | localhost:3000 and krishi-ai-sigma.vercel.app |
| Locales | en, 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 rule | How this guide uses it |
|---|---|
| Short sentences | Procedure sentences have 20 words or fewer. Descriptive sentences have 25 words or fewer. |
| One instruction per step | Each numbered step gives one action. Commands use the imperative (“Open”, “Type”, “Click”). |
| Active voice | The 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 notices | Warning = risk of harm, data loss or a broken feature. Caution = risk of a wrong result. Note = useful information. |
| Technical names | Code 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.