greenproof-ml / README.md
Elraphaa's picture
Generate advice automatically when a check-in is scored
11dfae8 verified
|
Raw
History Blame Contribute Delete
5.14 kB
metadata
title: GreenProof Verification
emoji: 🌳
colorFrom: green
colorTo: gray
sdk: docker
app_port: 7860
pinned: false

GreenProof verification service

Scores a tree check-in and writes back a confidence, a verdict and a per-signal breakdown. The only server-side surface in the stack.

Why this exists

Everything else in GreenProof is Supabase called directly from the browser. This service exists for one reason: a client-computed verification result is forgeable. The anon key ships inside the PWA and must be assumed public, so confidence, verdict and signals are not granted to authenticated at the column level. This process holds the only key that can write them.

POST /score takes an id and nothing else. Every input is re-read from the database and from storage β€” a caller can ask for a check-in to be scored, but can never influence what the score is.

Endpoints

Method Path Purpose
GET /health liveness, model-loaded, config present
POST /score score one check-in β€” {"checkin_id": "..."}
POST /advise species + care advice β€” advisory, never a verdict
POST /backfill?limit=50 score everything still pending
GET /docs interactive OpenAPI

/advise is the only endpoint that is not verification. It writes species_guess and advice and cannot touch confidence, verdict or signals β€” enforced by column grants, and by pipeline.py never importing the advisor. It is also the only endpoint that spends money (~3 cents a call), which is why it can be locked behind ADVISE_TOKEN.

How a check-in is judged

Stage 1 β€” gates. Disqualifying on their own, run before any scoring.

Gate Catches
duplicate image (pHash) resubmitted photo, gallery photo, internet photo
GPS radius right tree, wrong place
travel speed one account submitting from two impossible places

Stage 2 β€” scores. Continuous 0–1 signals, weighted into a confidence.

Score Signal
location distance from the registered point
liveness excess-green vegetation fraction
scene match DINOv2 cosine + ORB/RANSAC inliers vs previous visits
growth canopy and trunk plausibility, not measurement

Five of the six fraud types in our attack set are caught in stage 1 by ordinary deterministic code. That is the honest reason the system works, and it is why overall accuracy is much better than the 54% rank-1 of image matching alone.

Honest limits

  • Image matching is corroboration, not identity. Measured on our own 15 plants: rank-1 54% against 5% chance, AUC 0.77 β€” real signal, nowhere near enough to authorise a payout. It is bimodal: near-perfect on distinctive trees, near-zero inside a dense same-species stand.
  • Wide-shot matching works partly off the background, not the tree.
  • Young bark is smooth; BarkNet's ~94% figures are on mature bark.
  • Thresholds and weights in scoring.py are provisional. They are replaced by weights fitted under leave-one-tree-out cross-validation once real rounds exist. Nothing is tuned on the data used to report performance.
  • A missing signal lowers confidence and routes to a human. It never counts as zero, and it never fails closed on a genuine visit. Designed to degrade.

Configuration

Space β†’ Settings β†’ Variables and secrets:

Name Kind Value
SUPABASE_URL variable your project URL
SUPABASE_SERVICE_KEY secret the service_role key
GEMINI_API_KEY secret for /advise β€” free tier, the default provider
ANTHROPIC_API_KEY secret alternative provider, used only if no Gemini key
ADVISOR_PROVIDER variable optional: pin to gemini or anthropic
ADVISE_TOKEN secret any random string; required in X-Advise-Token on /advise
ALLOWED_ORIGINS variable your Vercel URL

The service key must never appear in the frontend. It can write any verdict for any tree, and it is the value the whole security model rests on.

Two providers, one interface. Advice runs on Gemini's free tier by default, and falls back to Anthropic if only that key is present. Free tiers have closed under this project three times mid-build, so a second provider is the cheapest insurance against a fourth β€” and switching is one environment variable, not a code change.

Setting neither is a supported state, not a failure: /score is unaffected and /advise returns {"written": false}. The advisory layer fails soft by design so it can never take verification down with it.

ADVISE_TOKEN matters on a public Space. Without it, anyone who finds this URL and a valid check-in id can spend your Anthropic credit three cents at a time. /score needs no such lock: it costs only our own CPU.

Local run

cd ml
pip install -r requirements.txt
export SUPABASE_URL=... SUPABASE_SERVICE_KEY=...
uvicorn app:app --reload --port 7860