Donation intake and inventory intelligence system for WellSpring Women's Center, Sacramento.
- Intake — staff scan barcodes or manually enter donated items on their phone
- Classification — Gemini 2.5 Flash automatically sorts items into categories
- Review — low-confidence items go to a human review queue
- Dashboard — category breakdowns and weekly trends
- Give Out — record items distributed to guests
- Ask — natural-language questions answered from live inventory data
| Layer | Technology |
|---|---|
| Frontend | React + Vite + TailwindCSS → Cloudflare Pages |
| Backend | AWS Lambda (Node 20) behind API Gateway → Serverless Framework v3 |
| Database & Auth | Supabase (Postgres + RLS + Auth) |
| AI | Gemini 2.5 Flash (classification + Q&A) |
| Barcode scanning | ZXing WASM (in-browser) |
VITE_SUPABASE_URL=https://<project>.supabase.co
VITE_SUPABASE_ANON_KEY=<anon key>
VITE_API_BASE_URL=https://<api-gateway-id>.execute-api.us-west-2.amazonaws.com/dev
SUPABASE_URL
SUPABASE_SERVICE_ROLE_KEY
GEMINI_API_KEY
OLLAMA_HOST (default: http://localhost:11434)
pnpm install
pnpm --filter web dev # frontend on http://localhost:5173Run these in PowerShell from the repo root every time you change anything in apps/api/:
$env:SUPABASE_URL = "https://<project>.supabase.co"
$env:SUPABASE_SERVICE_ROLE_KEY = "<service role key>"
$env:GEMINI_API_KEY = "<gemini key>"
$env:OLLAMA_HOST = "http://localhost:11434"
cd apps/api
pnpm build
serverless deploypnpm build runs esbuild and outputs a single dist/index.js.
serverless deploy packages and uploads it to AWS Lambda.
After a successful deploy, Serverless prints the new API Gateway URL. If it changed, update VITE_API_BASE_URL in apps/web/.env and redeploy the frontend.
Run this from the repo root every time you change anything in apps/web/:
cd C:\Users\USER\Documents\GitHub\HerLedger
pnpm --filter web build
wrangler pages deploy apps/web/dist --project-name herledger --commit-dirty=truepnpm --filter web build compiles the React app into apps/web/dist/.
wrangler pages deploy uploads it to Cloudflare Pages.
Migrations live in packages/db/migrations/. They are numbered and applied in order. Never edit an already-applied migration — create a new one instead.
To apply a new migration, paste its contents into the Supabase SQL Editor and run it.
Current migrations:
| File | What it does |
|---|---|
0001_initial_schema.sql |
Core tables: users, items, categories, item_classifications, daily_snapshots, audit_log |
0002_rls_policies.sql |
Row Level Security policies |
0003_functions.sql |
refresh_daily_snapshots, forecast_days_remaining, retrieve_chunks |
0004_user_profile_trigger.sql |
Auto-creates a public.users row on Supabase Auth signup |
0005_classification_runs.sql |
Tracks each classification run (status, items processed) |
0006_distributions.sql |
Records items given out to guests |
Seed data (run once after initial setup):
packages/db/seed/categories.sql — inserts all 30 item categories
- Staff scan items in Intake — each item is saved as
pending - Press Run Classification on the Dashboard
- Lambda calls Gemini 2.5 Flash for each pending item
- Items classified with confidence ≥ 75% →
classified - Items below threshold →
needs_review(appear in Review queue) - After all items are processed,
refresh_daily_snapshots()updates the dashboard
Gemini free tier: 20 requests/day for
gemini-2.5-flash. Enable billing at aistudio.google.com for production use.
"0 items classified" after pressing Run Classification
Items are not in pending status. Run in Supabase SQL Editor:
UPDATE items SET status = 'pending'
WHERE id NOT IN (SELECT DISTINCT item_id FROM item_classifications);Classification keeps looping / never finishes
Items stuck as needs_review with no classification record. Same fix as above.
Dashboard shows wrong counts Refresh the snapshot manually:
SELECT refresh_daily_snapshots();429 Too Many Requests from Gemini Free tier quota exhausted (20 req/day). Wait until tomorrow or enable billing.
API returns 500
Check AWS CloudWatch: Console → CloudWatch → Log groups → /aws/lambda/herledger-api-dev-api → latest log stream.