DEV Community

Tharun
Tharun

Posted on

The Rules Decide, Gemma Explains: A Garden Planner That Sends You Outside

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass

What I Built

In cricket, the third umpire watches the replay and gives the decision: out or not out. The commentators explain why, in friendly words. Nobody ever lets the commentator give the batter out.

That one rule is the whole design of GrowWise.

GrowWise is a garden planner for anyone with a balcony, a few pots, a sunny windowsill or a small plot, who wants less time on the phone and more time with their hands in soil. You tell it what kind of space you have, how much sun it gets and how big it is. Then:

  • Plain rules decide which of 12 plants can actually live in your space.
  • Every "no" comes with a reason, in words a beginner can read.
  • You get a four-week plan measured in minutes outdoors, not minutes in the app.
  • A small open-weight model, Gemma 2 2B, can explain the plan in friendly language, running on your own laptop. If no model is running, everything still works.

The model is the commentator. The rules are the umpire.

How it gets you off the screen

The output is not a chat. It is a to-do list for your hands. Most apps want you to scroll longer. GrowWise wants you to close the tab.

For a 12 sq ft balcony with partial sun, GrowWise picks four plants: French breakfast radish (a small cousin of mooli), baby spinach (palak), peppermint (think pudina) and butterhead lettuce. It then asks for 340 minutes outside over four weeks: 100 in week one, 60 in week two, 80 in week three and 100 in week four. Every task lists the supplies you need and a safety note. A tracker adds up the minutes you log outdoors. It never measures how long you spend looking at the app.

Where it says no

You know how a food delivery app sometimes says you are outside its service area, and nothing else? That feeling of being told no without a reason is what I wanted to avoid. In GrowWise, every rejected plant explains itself:

Your setup Plant What GrowWise tells you
Balcony, partial sun Cherry tomato Insufficient sunlight: Requires 6.5h direct sun, but your space receives partial_sun (~4.5h).
Backyard bed Peppermint Excluded from in-ground plots: Peppermint root rhizomes spread aggressively and invade beds.

Anyone who has planted pudina straight into the ground knows why that second rule exists. And on a dim kitchen windowsill (indoor, shade, 4 sq ft), exactly one of the twelve plants passes: baby spinach.

GrowWise is built for beginners with small spaces. These are the people who buy a plant, watch it die and decide they have no green thumb, when often the plant was never going to survive that spot.

Demo

Live app: growwise-84y9.onrender.com

I Took It Outside

I have a small garden at home in West Godavari, in Andhra Pradesh. Some time ago I planted a plant there. After some days, it died.

A dying plant does not send a WhatsApp message saying "too little sun, please help". It just dies, and you are left guessing. If you have ever watched a plant fail and decided you have no green thumb, you know that feeling.

Then I built GrowWise, and I typed in my own garden:

  • Location: West Godavari
  • Garden: a small home garden
  • Sunlight: partial sun

The answer was clear. Eight of the twelve plants failed the sunlight check the moment I chose partial sun, and every one of them came with a reason in plain words. They want five to six and a half hours of direct sun, and GrowWise counts partial sun as about four and a half. Only a small handful of plants passed, and spinach (palak) and lettuce were on that list.

That is when I understood why my plant died. It was not bad luck, and my garden was not cursed. The plant and the spot never matched, and I could not see that until GrowWise showed me.

GrowWise also got something wrong, and I want to be honest about it. It ignored "West Godavari". The rules only use the garden type, the sunlight and the size, so it cannot yet tell me whether this is the right season to plant. That is the next thing I want to fix.

GrowWise did not bring my plant back. It gave me something more useful: the reason, and a list of plants that can really live in my garden. From now on I choose the plant after I know my spot, not before.

The screen gave me the answer. The rest of the work happens in the soil.

Code

GitHub logo tharun-pandya / GrowWise-

Local garden planner

🌱 GrowWise: Grounded AI-Powered Garden Planner

Hacktoberfest 2026 Frontend Backend Core AI Tests

A lightweight, transparent, and grounded garden planner built for the Hacktoberfest 2026 Open-Source AI Challenge, Week 1: Touch Grass Built exclusively with React + TypeScript + Vite on the frontend and Python FastAPI on the backend. Designed for deployment on Render.


🌟 Key Features

  1. Modern React + TypeScript + Vite Frontend
    • Nature-inspired design system with responsive desktop and mobile layouts.
    • Interactive garden profile with 8 validated growing conditions.
    • 4-Week actionable outdoor task planner with session tracking and Markdown export.
    • Transparent disqualification logs: see exactly why a crop failed without black-box surprises.
  2. Deterministic Botanical Rules Engine:
    • Sunlight, container depth, soil requirements, and USDA Hardiness Zones evaluated with strict boolean logic.
    • Sourced from 12 authoritative agricultural extension guides (USDA, Cornell, Penn State, UMN, UC Davis, Purdue, etc.).
  3. Open-Weight Gemma 2 2B Architecture
    • Primary AI: Gemma 2 2B running via Ollama or…

The repo in five lines:

data/                    crops.json and gardening_sources.json (12 crops, 12 sources)
backend/app/             FastAPI: rules engine, model provider, safety check
src/                     React + TypeScript + Vite front end (the rules run here too)
tests/ backend/tests/    23 unit tests
render.yaml, build.sh    one-service deploy on Render
Enter fullscreen mode Exit fullscreen mode

Run it yourself:

git clone https://github.com/tharun-pandya/GrowWise-.git
cd GrowWise-
npm install
npm run dev        # the app runs on http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Run the tests:

python3 -m unittest backend/tests/test_backend.py tests/test_knowledge_base.py tests/test_rules.py tests/test_ai_engine.py -v
Enter fullscreen mode Exit fullscreen mode

The last lines of the output:

Ran 23 tests in 0.036s

OK
Enter fullscreen mode Exit fullscreen mode

How I Built It

The numbers first: 12 crops, 12 sources, 23 tests, 120 gardens cross-checked, 340 outdoor minutes in the sample plan, a model file under 2 GB, $0 to run. Now the story.

The idea in one picture

 your answers (garden type, sun, size)
                 |
                 v
  RULES (plain if-statements, 12 crops)    <- the umpire decides
                 |
                 +--> plants that pass, with a reason for every "no"
                 v
  PROMPT built only from the plants that passed
                 |
                 v
  EXPLAINER                                <- the commentator talks
     offline template   (no model, always works)
     or Gemma 2 2B      (open-weight, runs on your machine)
                 |
                 v
  4-week plan in minutes outdoors --> you go outside
Enter fullscreen mode Exit fullscreen mode

Step 1: the rules, as a plain loop

Before any AI, a garden planner has to answer a boring question: can this plant live here? That is not a language problem. It is three comparisons. Does the plant suit this kind of garden? Is there enough sun? Is there enough room?

Your sunlight answer becomes hours: full sun counts as 7, partial sun as 4.5 and shade as 2.5. Each crop has a minimum, with a small allowance of a quarter hour (half an hour for shade-loving plants). Here is the heart of it, trimmed to the essentials:

import json

SUN_HOURS = {"full_sun": 7.0, "partial_sun": 4.5, "shade": 2.5}

def check_crop(crop, garden_type, sunlight, space_sq_ft):
    reasons = []                                    # why this crop is rejected

    if garden_type not in crop["compatible_garden_types"]:
        reasons.append(f"Not suitable for a {garden_type}.")

    if garden_type == "backyard" and crop["id"] == "peppermint":
        reasons.append("Peppermint spreads underground and takes over the bed.")

    sun_you_have = SUN_HOURS[sunlight]
    tolerance = 0.5 if crop["sunlight"] == "shade" else 0.25
    if sun_you_have < crop["min_sunlight_hours"] - tolerance:
        reasons.append(f"Needs {crop['min_sunlight_hours']}h of sun, you get about {sun_you_have}h.")

    if space_sq_ft < crop["min_space_sq_ft"]:
        reasons.append(f"Needs {crop['min_space_sq_ft']} sq ft, you have {space_sq_ft}.")

    return reasons                                  # empty list means: grow it

crops = json.load(open("data/crops.json"))

for crop in crops:
    reasons = check_crop(crop, "balcony", "partial_sun", 12)
    if reasons:
        print("NO ", crop["common_name"], "->", " | ".join(reasons))
    else:
        print("YES", crop["common_name"])
Enter fullscreen mode Exit fullscreen mode

Running it on a 12 sq ft balcony with partial sun:

NO  Sweet Basil -> Needs 6.0h of sun, you get about 4.5h.
NO  Cherry Tomato -> Needs 6.5h of sun, you get about 4.5h.
YES Butterhead Lettuce
YES Baby Spinach
YES Peppermint
YES French Breakfast Radish
NO  Bell Pepper -> Needs 6.5h of sun, you get about 4.5h.
NO  Danvers Half-Long Carrot -> Not suitable for a balcony. | Needs 6.0h of sun, you get about 4.5h.
NO  Bush Green Bean -> Needs 6.0h of sun, you get about 4.5h.
NO  Tuscan Rosemary -> Needs 6.5h of sun, you get about 4.5h.
NO  Alpine Strawberry -> Needs 5.0h of sun, you get about 4.5h.
NO  French Marigold -> Needs 6.0h of sun, you get about 4.5h.
Enter fullscreen mode Exit fullscreen mode

The real engine in the repo also adds a score and a "why it passed" list, but the pass or fail logic is exactly this.

The same rules live in two places: Python (the API) and TypeScript (your browser). I compared them on 120 gardens: 5 garden types, 3 sunlight levels and 8 sizes. They picked the same crops every time. The browser copy means the planner keeps working after the page loads, even if your connection drops.

The facts live in data/crops.json, and each crop points to a source in data/gardening_sources.json: USDA Cooperative Extension, Cornell, Penn State, the University of Minnesota, the Royal Horticultural Society, Purdue, the University of Florida, Oregon State, Iowa State, Clemson and NC State.

Step 2: a small open model with a small job

An open-weight model is one whose trained weights you can download and run yourself. Ollama is a free app that runs such models on your own computer. I used Gemma 2 2B (gemma2:2b). The file is under 2 GB, so it fits on an ordinary laptop, and I designed GrowWise for machines with 8 GB of RAM or less.

When you pick Local Gemma 2 2B (Ollama Endpoint) on the Weekly Plan page, your browser talks straight to Ollama on your own machine, at localhost:11434, using the OpenAI-style chat format that Ollama offers. Nothing goes through my server. This is what it sends, shortened to two of the four crops:

POST http://localhost:11434/v1/chat/completions
model: gemma2:2b        temperature: 0.2

system:
You are GrowWise, a grounded horticultural AI assistant for Hacktoberfest Touch Grass. Never hallucinate botanical facts. Rely strictly on provided context.

user:
GARDEN PROFILE:
Location: Pune
Garden Type: balcony
Sunlight: partial_sun
Area: 12 sq ft
Experience: beginner

VERIFIED SUITABLE CROPS:
- French Breakfast Radish (Raphanus sativus): Requires 4.5h sun, 0.2 sq ft, 6" root depth. Matures in 25 days. Guidance: Thin seedlings early to 2 inches apart to allow crisp root bulbing. Keep watering steady.
- Baby Spinach (Spinacia oleracea): Requires 3h sun, 0.5 sq ft, 6" root depth. Matures in 38 days. Guidance: Keep soil evenly damp. Does exceptionally well in dappled sunlight or balcony shade.
(two more crops in the same format)

TASK:
Explain why these crops suit this layout and provide 3 practical outdoor next steps to get outside and touch grass.
Enter fullscreen mode Exit fullscreen mode

Three things to notice:

  1. The model only sees plants that already passed. It cannot add a crop. It cannot remove one. The worst it can do is explain badly.
  2. The temperature is 0.2. A low temperature means less random wording.
  3. You can read the exact prompt yourself. The Inspect Grounded Prompt button shows it.

Can a 2B model still write a clumsy sentence? Of course. That is exactly why I never ask it to pick plants or to remember plant facts. A model this small is too small to trust with them. The facts come from a file, they are pasted into the prompt, and the model only does the talking.

The API also has an extra safety net. If you ask the server to explain a plan with a model, the answer goes through a small checker first. It looks for products I do not want near a beginner's balcony, like Roundup, glyphosate and bleach, and for medical claims like "cures cancer". One of the 23 tests hands it the sentence "Apply roundup and synthetic pesticide across the soil bed" and checks that it gets blocked. It is a blunt word list, not magic.

To try Gemma yourself:

ollama pull gemma2:2b     # one-time download, under 2 GB
npm run dev               # then choose "Local Gemma 2 2B" on the Weekly Plan page
Enter fullscreen mode Exit fullscreen mode

If your browser blocks the call to Ollama, add the page's address to Ollama's OLLAMA_ORIGINS setting.

Step 3: when there is no model

A model should never be the single point of failure in a garden app. So there is an offline explainer: a template that fills in the same verified facts with no model at all. It is also what the live demo uses by default, because a free Render instance has only 512 MB of memory, far too little for a language model. If you choose Gemma and Ollama is not running, the app says so in one line and shows the verified care notes instead. No error page, no spinner that never ends.

Step 4: the part for your hands

The plan runs four weeks, with one set of tasks per crop:

  • Week 1: setup and planting, 25 minutes
  • Week 2: a moisture check and thinning seedlings, 15 minutes (about one chai break)
  • Week 3: mulching and a pest check, 20 minutes
  • Week 4: pruning and the first harvest, 25 minutes

Every task lists supplies and a safety note. They are simple templates, the same shape for every crop. I would rather give you a plan that is easy to follow than a clever one you ignore. You can export it as Markdown, and the Outdoor Progress page logs the minutes you actually spend outside. Your plan and your minutes live in your browser's localStorage. No account, no copy on my server, and no tracking code anywhere in the project.

Step 5: shipping it on Render

GrowWise is one Render web service on the free plan. FastAPI serves the built React app and the API from a single URL, so there is no CORS puzzle and nothing to keep in sync. render.yaml describes the service. build.sh builds the front end, installs the Python packages and then runs all 23 tests, so if a rule breaks, the build fails and the broken version never goes live. A Run Test Suite (23 Tests) button in the top bar runs the same suite on the server and shows the output, so anyone can check the claims in this post.

What it does not do yet

I would rather tell you than have you find out:

  • It does not use your location yet. The form asks where you are, but only to label your plan. The rules look at garden type, sunlight and area. The crop data already carries hardiness zones, but the rules do not read them. So no frost dates and no "what to plant this week" yet.
  • The database is small. Twelve crops, mostly vegetables and herbs from US and UK guides. A balcony in Pune and a balcony in Seattle get the same rules.
  • The live demo uses the offline explainer. Gemma needs your own Ollama.
  • The safety word list is not in the browser path yet. The Gemma button talks to Ollama directly, so it does not pass through the checker on the API.
  • The rules protect the plants, not the prose. A 2B model can still write a clumsy paragraph. I did not benchmark any model, so everything here is a design argument, not a leaderboard.

What is next

Zones, frost dates and seasons, so GrowWise can answer "what do I plant this week?". Crops that people actually grow in Indian homes, like tulsi, methi, coriander and genda. And the safety check inside the browser path. In the next post I will add those to the rules. The model will still not be allowed near them.

Why Does Open Innovation Matter?

The challenge asks where an open approach beat a closed one. Here are straight answers, including the parts where I have no proof.

Does it work without internet? Mostly, yes, and a hosted model could not. After the page loads, the rules, the plan and the offline explainer all run in your browser, so you can plan on a balcony with no signal. Run GrowWise locally with Ollama and the model runs on your laptop too. A hosted model needs a connection for every sentence.

Whose server sees my garden? Nobody's. In offline mode and in Gemma mode, your answers never leave your browser and your own machine. The profile and tracker sit in localStorage, and there are no accounts. With a closed API, every prompt would travel to a company's server.

Can I swap the model? On the server, yes, with two settings. The model provider reads its endpoint and model name from environment variables (OPEN_WEIGHT_ENDPOINT_URL and OPEN_WEIGHT_MODEL_NAME) and speaks the chat format that Ollama, vLLM and many hosts understand. Today it is gemma2:2b. Tomorrow it can be a bigger Gemma or a different open model, and the rules do not change. The browser button is fixed to Ollama and gemma2:2b for now.

Does it cost anything? No. A free Render plan, no API key and no per-token bill. The offline explainer is free, and Gemma on my own laptop costs electricity.

Where did open work better? I did not test a closed model, and a big hosted one would probably write a prettier paragraph. The real win was the design. Because Gemma 2 2B is too small to trust with facts, I never asked it to pick plants or remember any. The rules decide, the model explains, and the app still works with the model switched off. A bigger hosted model makes it very easy to skip that discipline, and a garden app that confidently tells a beginner the wrong thing about mint is worse than no app.

An open model will not always write the nicest sentence. It does let me decide where the sentence is written, what it is allowed to say and who gets to see it. For a garden app, that was the better trade.

My Agent Session

I built GrowWise in Google AI Studio, using it as my AI coding assistant to design the rules engine, the TypeScript types and the unit tests. It is a development tool. The deployed app does not call it or any other closed AI service.

Prize Categories

  • Best Use of Gemma: Gemma 2 2B (gemma2:2b) is the open-weight model behind the explainer, run locally through Ollama. The server side can point at any OpenAI-compatible Gemma endpoint.
  • Best Use of Render: The whole app, front end and API, runs as one free Render web service, deployed from render.yaml, with a health check and a build that runs the tests.

Thanks for reading. Now close the laptop and go touch some grass.

Top comments (1)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •
You need to verify your account.
Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to