File size: 8,902 Bytes
1d02209
 
60e41bb
 
 
1d02209
60e41bb
 
 
 
fd2abad
 
 
 
 
 
deac482
 
1d02209
 
60e41bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d70a4af
60e41bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deac482
60e41bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6164f6b
60e41bb
6164f6b
 
60e41bb
 
6164f6b
 
 
 
 
 
 
 
 
 
 
60e41bb
 
6164f6b
60e41bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deac482
 
 
60e41bb
 
 
d70a4af
 
60e41bb
 
 
deac482
 
 
 
 
 
 
 
 
 
60e41bb
deac482
60e41bb
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
---
title: Waterleaf
emoji: 🌿
colorFrom: green
colorTo: red
sdk: docker
app_port: 7860
hf_oauth: true
hf_oauth_expiration_minutes: 43200
license: mit
short_description: Plant ID and weather-aware watering calendars
tags:
  - track:backyard
  - sponsor:modal
  - sponsor:openai
  - achievement:llama
  - achievement:fieldnotes
  - achievement:bestdemo
---

# Waterleaf

Waterleaf identifies an outdoor garden plant from one to three photographs,
grounds the result in a plant taxonomy database, builds an editable
weather-aware watering plan, and exports one 30-day calendar for the garden.

Built by Hakan Karaoguz (`hkaraoguz`) for the 2026 Hugging Face Build Small
Hackathon.

## Workflow

1. Upload or capture one to three photographs of one plant.
2. Gemma 4 extracts visible traits and proposes likely names.
3. GBIF resolves those names to valid plant species.
4. Gemma reranks only the valid records.
5. Confirm or replace the species through autocomplete.
6. Preview weather-adjusted dates and edit them.
7. Save plants and export one whole-garden ICS file.

## Architecture

- **UI and web:** Gradio Blocks mounted in FastAPI
- **Authentication:** Hugging Face OAuth
- **Persistence:** SQLite and normalized JPEGs on an attached HF Storage Bucket
- **Vision model:** `ggml-org/gemma-4-26B-A4B-it-GGUF`
- **Runtime:** llama.cpp `server-cuda13-b9445` on a Modal L4
- **Taxonomy:** GBIF Species API
- **Care data:** Small local baseline catalog with manual interval fallback
- **Weather:** Open-Meteo geocoding and 16-day forecast
- **Calendar:** RFC 5545-compatible ICS with stable UIDs, alarms, profile URLs,
  and image attachments

See [docs/architecture.md](docs/architecture.md) for the data flow and privacy
boundaries.

## Local Development

Python 3.11-3.13 and `uv` are supported.

```bash
uv sync
uv run uvicorn app:app --host 0.0.0.0 --port 7860
```

Open `http://localhost:7860`. Without `MODAL_ENDPOINT`, Waterleaf uses a
deterministic lavender demo identifier. Local persistence uses the
`local-gardener` identity and `data/` directory.

Run checks:

```bash
uv sync
uv run pytest
uv run ruff check .
```

## Modal Deployment

### 1. Authenticate the Modal CLI

Install the deploy dependency and connect the local CLI to the Modal workspace:

```bash
uv sync --group deploy
uv run --group deploy modal setup
```

### 2. Deploy llama.cpp

Deploy the protected GPU service:

```bash
uv run --group deploy modal deploy modal_app.py
```

The Modal service:

- uses the pinned `ghcr.io/ggml-org/llama.cpp:server-cuda13-b9445` image;
- starts `Gemma 4 26B-A4B Q4_K_M` with automatic multimodal projector download;
- uses an 8K context, full GPU offload, Flash Attention, Q8 KV cache, and one
  parallel slot;
- uses a bounded 256-token thinking pass for database-candidate reranking while
  keeping initial visual extraction non-thinking and schema-constrained;
- caches Hugging Face artifacts in a Modal Volume;
- requires Modal proxy-auth headers.

The command prints the `modal.run` URL. Save it as `MODAL_ENDPOINT`.

For the live demo and judging window, keep one container warm:

```bash
MODAL_MIN_CONTAINERS=1 uv run --group deploy modal deploy modal_app.py
```

Return to zero warm containers after judging to stop idle GPU spend:

```bash
MODAL_MIN_CONTAINERS=0 uv run --group deploy modal deploy modal_app.py
```

### 3. Create proxy credentials

In Modal Workspace Settings, create a **Web endpoint proxy auth token**. Save
the token ID as `MODAL_KEY` and token secret as `MODAL_SECRET`. These are not
the same credentials used by `modal setup`.

Test the endpoint before configuring the Space:

```bash
MODAL_ENDPOINT=https://...modal.run \
MODAL_KEY=wk-... \
MODAL_SECRET=ws-... \
uv run python scripts/smoke_modal.py assets/sample-lavender.png
```

If a proxy token has not been created yet, a controlled one-time smoke test can
temporarily publish the endpoint:

```bash
MODAL_PROXY_AUTH=0 MODAL_MIN_CONTAINERS=1 \
  uv run --group deploy modal deploy modal_app.py
MODAL_ENDPOINT=https://...modal.run \
  uv run python scripts/smoke_modal.py assets/sample-lavender.png
MODAL_PROXY_AUTH=1 MODAL_MIN_CONTAINERS=0 \
  uv run --group deploy modal deploy modal_app.py
```

The middle deployment is unauthenticated and should exist only for the smoke
test. Always run the final restore command immediately afterward.

## Space Configuration

### 1. Create the Space

Create `build-small-hackathon/waterleaf` in the Hugging Face UI with:

- **SDK:** Docker
- **Visibility:** Public
- **License:** MIT

If the hackathon organization does not allow direct creation, create
`hkaraoguz/waterleaf` first and transfer or duplicate it into the requested
hackathon namespace.

The root README metadata already enables Docker on port `7860` and HF OAuth.

### 2. Upload this repository

Authenticate the Hugging Face CLI and upload the working tree. The CLI sends
binary assets through Xet storage, which a plain Git push does not:

```bash
hf auth login
hf upload build-small-hackathon/waterleaf . . \
  --repo-type space \
  --exclude '.git/**' \
  --exclude '.venv/**' \
  --exclude '.pytest_cache/**' \
  --exclude '.ruff_cache/**' \
  --exclude '**/__pycache__/**' \
  --exclude '*.pyc' \
  --exclude 'data/**' \
  --exclude '.env'
```

Do not commit or upload local environment files or deployment secrets.

### 3. Attach persistent storage

In **Space Settings → Storage Buckets**:

1. Create or select a bucket for Waterleaf.
2. Attach it read-write.
3. Set the mount path to `/data`.

The Docker image already sets `WATERLEAF_DATA_DIR=/data`. Without this mount,
saved gardens and images disappear when the Space restarts.

### 4. Configure secrets and variables

In **Space Settings → Variables and secrets**, add:

| Name | Type | Required | Purpose |
| --- | --- | --- | --- |
| `MODAL_ENDPOINT` | Secret | Production | Protected llama.cpp base URL |
| `MODAL_KEY` | Secret | Production | Modal proxy token ID |
| `MODAL_SECRET` | Secret | Production | Modal proxy token secret |
| `PUBLIC_BASE_URL` | Variable | Optional | Override the derived Space URL |
| `WATERLEAF_DATA_DIR` | Variable | No | Defaults to `/data` in Docker |

Use only the base Modal URL for `MODAL_ENDPOINT`; do not append
`/v1/chat/completions`.

### 5. Rebuild and verify

Trigger **Factory reboot** after attaching storage or changing secrets. Then
verify:

```bash
curl --fail https://build-small-hackathon-waterleaf.hf.space/health
```

Expected response:

```json
{"status":"ok"}
```

Open the Space directly, not only inside the Hub iframe, and check:

1. **Sign in with Hugging Face** completes successfully.
2. A guest can preview one identification.
3. A signed-in user can save a plant and see it after a factory restart.
4. The generated ICS downloads and its public plant/image links open.

Guests may run one temporary identification preview. Login is required to
save, delete, or export plants.

## Evaluation

Populate `evaluation/manifest.csv` with at least 20 consented real-garden
examples and run:

```bash
MODAL_ENDPOINT=... MODAL_KEY=... MODAL_SECRET=... \
  uv run python scripts/evaluate.py evaluation/manifest.csv
```

The report includes species top-1/top-3 accuracy, genus top-1 accuracy,
per-case predictions, and latency. A live one-to-three-photo smoke test is
available at `scripts/smoke_modal.py`.

## Privacy and Limitations

- Images are resized, converted to JPEG, and stripped of EXIF.
- Stored coordinates are rounded and never exposed on public plant pages.
- Public pages use opaque slugs but are intentionally public for calendar use.
- Plant identification and watering dates are suggestions, not horticultural
  guarantees.
- If forecast retrieval fails after location resolution, Waterleaf still
  generates dates from the care baseline and labels them as seasonal estimates.
- Geocoding failure stops schedule preview until the location is corrected.
- Dates after the 16-day forecast are labeled seasonal estimates.
- ICS `ATTACH` support varies by calendar client; every event also includes a
  portable public profile link.
- Users must provide a manual interval when the local catalog has no care
  baseline for the selected species.

## Submission Materials

- [30-second demo video](https://www.youtube.com/watch?v=4H5vGVFcaO4)
- [X post](https://x.com/hknkrgz/status/2066605985741807972)
- [Field Notes](docs/field-notes.md)
- [Architecture](docs/architecture.md)

Target categories: Backyard AI, Best Demo, Llama Champion, Modal-powered, and
Field Notes. Waterleaf does not claim Off the Grid because inference, taxonomy,
and weather are cloud-hosted.

## Team

- [Hakan Karaoguz (`hkaraoguz`)](https://huggingface.co/hkaraoguz)

## Credits

- [Gemma 4](https://huggingface.co/google/gemma-4-26B-A4B-it)
- [llama.cpp](https://github.com/ggml-org/llama.cpp)
- [GBIF](https://www.gbif.org/developer/species)
- [Open-Meteo](https://open-meteo.com/)