Skip to content

PhilRice-CITU/api-server

Repository files navigation

Rice Vision API Server

FastAPI backend that sits at the centre of the Rice Vision system. It connects Raspberry Pi edge devices to the React web dashboard, using Supabase as the data layer (PostgreSQL + Storage).

Raspberry Pi Edge Client
        │  POST /scans (multipart images + metadata)
        ▼
  Rice Vision API  ◄──── Bearer JWT ────  React Web Dashboard
        │
        ▼
  Supabase (PostgreSQL + Storage)

Prerequisites


Setup

1. Create a virtual environment

cd api-server
python -m venv .venv
source .venv/bin/activate        # macOS / Linux
# .venv\Scripts\activate         # Windows

2. Install dependencies

pip install -e ".[dev]"

3. Configure environment variables

cp .env.example .env

Open .env and fill in your Supabase credentials:

Variable Where to find it
SUPABASE_URL Supabase Dashboard → Project Settings → API → Project URL
SUPABASE_SERVICE_ROLE_KEY Supabase Dashboard → Project Settings → API → service_role key
SUPABASE_JWT_SECRET Supabase Dashboard → Project Settings → API → JWT Settings

The service role key bypasses Row Level Security. Keep it secret — never expose it to the frontend.

4. Run the development server

uvicorn app.main:app --reload --host 0.0.0.0 --port 3001

API Overview

Health

Method Path Auth Description
GET /health None Liveness check

Scans (Edge Client → API)

Method Path Auth Description
POST /scans None (device_id in body) Ingest scan from edge device

POST /scansmultipart/form-data

Field Type Description
raw File White-light (LED) JPEG
ir File Infrared (NoIR) JPEG
device_id string Device UUID (must exist in devices table)
session_id string UUID generated by the edge client
captured_at string ISO 8601 timestamp
mode string production (default)

Devices

Method Path Auth Description
GET /devices JWT List devices (region-scoped for admins)

Results

Method Path Auth Description
GET /results JWT Paginated scan results with filters
PATCH /results/{id} JWT Update rice_variety or operator_name

GET /results query params: device_id, start_date, end_date, page, page_size

Analytics

Method Path Auth Description
GET /analytics JWT Aggregated quality metrics

GET /analytics query params: start_date, end_date, device_id, region_id

Regions

Method Path Auth Description
GET /regions JWT (superadmin only) List all regions

Auth Model

Two separate auth paths:

Dashboard users — Supabase JWT
The React frontend sends Authorization: Bearer <supabase_access_token> on every request after login. FastAPI verifies the JWT using SUPABASE_JWT_SECRET and loads the user's role and region_id from the users table.

  • admin — can only see data scoped to their region_id
  • superadmin — can see all regions

Edge devices
Edge devices currently upload scan payloads with device_id in the request body for ingest endpoints.


Project Structure

Layered architecture: HTTP → routers → services → repositories → Supabase, with adapters beside services for external systems (vision model, Roboflow).

api-server/
├── pyproject.toml          ← Python project metadata & dependencies
├── .env.example            ← Environment variable template
├── .env                    ← Local config (git-ignored)
├── tests/
│   └── test_layering.py    ← Static checks that pin the architecture in place
└── app/
    ├── main.py             ← FastAPI factory, CORS, lifespan, router mounts
    ├── config.py           ← pydantic-settings; all env vars
    ├── dependencies.py     ← get_supabase, get_current_user
    │
    ├── routers/            ← HTTP layer (no DB, no business logic)
    │   ├── edge/           ← X-Device-ID auth (Raspberry Pi caller)
    │   │   ├── scans.py    ← POST /scans, /scans/batch (legacy edge upload)
    │   │   ├── sessions.py ← /edge/v1/sessions/...
    │   │   ├── devices.py  ← /edge/v1/devices/{provision,claim,status,upload-training}
    │   │   └── deps.py     ← require_device(X-Device-ID) FastAPI dep
    │   └── dashboard/      ← Supabase JWT auth (web caller)
    │       ├── results.py, devices.py, analytics.py, events.py,
    │       ├── regions.py, suggestions.py
    │
    ├── services/           ← business logic, orchestration
    │   ├── scan_service.py, grading_service.py, annotation_service.py,
    │   ├── result_service.py, device_service.py,
    │   ├── device_provisioning_service.py, device_event_service.py,
    │   ├── device_auth_service.py, analytics_service.py,
    │   ├── training_upload_service.py
    │
    ├── repositories/       ← only modules allowed to call supabase
    │   ├── results_repo.py, result_images_repo.py, corrections_repo.py,
    │   ├── devices_repo.py, device_events_repo.py,
    │   ├── device_secrets_repo.py, edge_sessions_repo.py,
    │   ├── regions_repo.py, suggestions_repo.py,
    │   ├── analytics_repo.py, storage_repo.py
    │
    ├── adapters/           ← external systems
    │   └── roboflow.py          ← Roboflow upload API
    │
    ├── grading/            ← YOLOv8 ONNX inference + PNS/BAFS 290:2025 grading
    │   ├── inference.py    ← RiceGrader (model load, detect, merge, post-process)
    │   ├── grader.py       ← grade_from_per_grain, GRADE_THRESHOLDS, PARAMETER_ORDER
    │   ├── features.py     ← per-grain feature extraction, PX_PER_MM
    │   ├── constants.py    ← MASS_PER_MM2 calibration
    │   └── report.py       ← build_payload, build_report, save_excel
    │
    ├── schemas/            ← Pydantic models, no logic
    │   └── results.py, scans.py, corrections.py, analytics.py, devices.py,
    │       events.py, regions.py, suggestions.py, edge.py
    │
    └── utils/              ← pure helpers (no I/O, no supabase)
        ├── auth_roles.py, datetime_parsing.py, device_auth.py,
        ├── event_persistence.py, metrics.py, scoping.py

Layering rule: routers may not call supabase.table(...) — that's enforced by tests/test_layering.py and the TID251 ruff ban on app.routers imports from services/repos/adapters. For the layer-by-layer explainer, the audience-split rationale, the cross-cutting flows, and the "how to add a new endpoint" recipe, see docs-and-architecture/api-server/architecture.md.


Development

Linting:

ruff check app/
ruff format app/

Running tests:

pytest

Generate TypeScript types for the web dashboard (run from web-dashboard/):

npx openapi-typescript http://localhost:3001/openapi.json -o src/api/types/openapi.d.ts

Known Schema Notes

  • results.operator_name is currently NOT NULL in the database schema but the edge client does not send it. It defaults to "" on ingest and should be updated to NULL-able if needed. Run the following in the Supabase SQL Editor:

    ALTER TABLE results ALTER COLUMN operator_name DROP NOT NULL;
  • Images are stored in the result-images Supabase Storage bucket as:

    • results/{result_id}/raw.jpg — white-light (LED), camera_type = 'led'
    • results/{result_id}/ir.jpg — infrared (NoIR), camera_type = 'noir'
  • Legacy preview relay and per-device secret columns were removed during earlier cleanup.


Device Event Lifecycle

The API uses a tiered event strategy to prevent unbounded growth in device_events:

  • Warm audit: only meaningful events are persisted in device_events.
  • Cold archive: raw event snapshots can be exported as daily NDJSON/GZIP files.

Warm audit persistence rules

  • Persist always: WARN, ERROR
  • Persist selected INFO: command lifecycle/status transition signals
  • Drop noisy operational INFO logs from persistence

Implementation files:

  • app/utils/event_persistence.py
  • app/routers/events.py
  • app/utils/device_events.py

Retention policy

Use tools/device_events_retention.sql in Supabase SQL Editor (or schedule via pg_cron) to keep:

  • INFO for 14 days
  • WARN/ERROR for 120 days

Cold archive helper

Run tools/archive_device_events.py on a schedule (daily) to export events to Supabase Storage:

python tools/archive_device_events.py

Environment variables used by the archiver:

  • SUPABASE_URL
  • SUPABASE_SERVICE_ROLE_KEY
  • DEVICE_EVENT_ARCHIVE_BUCKET (default: device-event-archives)
  • DEVICE_EVENT_ARCHIVE_DAYS (default: 1)
  • DEVICE_EVENT_ARCHIVE_BATCH_SIZE (default: 1000)

About

This repository is where the backend lives when processing the data, it is responsible for getting the image and sending it the the AI model for interpretation and this repository is also responsible for arranging data to be sent to the frontend.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages