# Canteen Asset Tracker — Project Structure (v3)

Three projects, shared DB.

## Repositories

```
~/projects/canteen-asset-tracker/           ← Main app (technician-facing: scan, check-in, assets, map, nav)
~/projects/canteen-asset-tracker-android/   ← Android app (Kotlin, Jetpack Compose)
~/projects/canteen-admin-server/            ← Admin app (users, customers, settings, exports, activity)
```

## Web App (`canteen-asset-tracker`)

```
canteen-asset-tracker/
├── server.py              FastAPI backend — core technician API routes (assets, checkins, visits, auth, OCR, upload)
├── openapi.yaml           **Shared API contract** — source of truth for both projects
├── assets.db              SQLite database (WAL mode, foreign keys on)
├── static/
│   └── index.html         Single-page frontend (technician UI: scan, assets, map, nav)
├── tests/                 pytest test suite
├── uploads/               User-uploaded photos
├── cert.pem / key.pem     Self-signed TLS certs
├── requirements.txt       Python deps (fastapi, uvicorn, pytesseract, pillow)
├── start.sh               Launch script (production)
└── smoke_test.sh          Smoke test suite
```

- **Port:** 8901 (HTTPS)
- **URL:** https://canteen.ourpad.casa (NPMPlus reverse proxy → backend :8901)
- **Frontend:** Single HTML file — no build step, no npm (~200KB, technician-focused).
- **Backend:** FastAPI with SQLite (WAL mode). Auth via Bearer tokens. OCR via Tesseract. EXIF GPS extraction via PIL.

## Admin App (`canteen-admin-server`)

```
canteen-admin-server/
├── admin_server.py        FastAPI backend — admin-only API routes (users, customers, locations, rooms, settings, activity, stats, exports, geofences)
├── static/
│   └── index.html         Single-page admin frontend (settings, user mgmt, activity, reports)
├── requirements.txt       Python deps (fastapi, uvicorn)
└── run.sh                 Launch script (port 8900)
```

Admin functions separated into their own project:
- **Port:** 8900
- **URL:** https://admin.canteen.ourpad.casa (NPMPlus proxy → backend)
- **Shared DB:** Uses same `assets.db` via `CANTEEN_DB_PATH` env var
- **Auth:** Same login system as main app (admin role required for admin endpoints)
- **Frontend:** Standalone SPA with Dashboard, Settings, Users, Customers/Locations, Activity Log, CSV Exports, DB Reset

## Android App (`canteen-asset-tracker-android`)

```
canteen-asset-tracker-android/
├── app/
│   ├── src/main/java/com/canteen/assettracker/
│   │   ├── network/
│   │   │   └── RetrofitModule.kt   API client config (points to web app URL)
│   │   ├── ui/
│   │   │   ├── login/              Login screen + ViewModel
│   │   │   ├── dashboard/          Dashboard screen + ViewModel
│   │   │   └── settings/           Settings screen + ViewModel
│   │   └── location/
│   │       └── ProximityEngine.kt  GPS proximity logic
│   └── build/                      Build outputs (APK, test reports)
├── build.gradle.kts                Root build config
├── settings.gradle.kts
├── gradlew / gradlew.bat           Gradle wrapper
└── keystore.properties             Signing keys
```

- **Package:** `com.canteen.assettracker`
- **API Base URL:** `https://canteen.ourpad.casa:8901/` (hardcoded in `RetrofitModule.kt`)
- **Tests:** 3 ViewModel/engine test classes, all passing (debug + release)
- **Built APK:** run `./gradlew assembleRelease`

## API Contract

`openapi.yaml` is the **single source of truth**. When you change the backend:

1. Update `server.py`
2. Update `openapi.yaml` to match
3. Android team (or future-you) can see exactly what changed

The spec covers:
- **48 named routes** across assets, check-ins, customers, locations, rooms, users, auth, geofences, visits, activity, stats, exports, uploads, and OCR
- **30 dynamic settings routes** (`/api/settings/{entity}` for 6 entity types)
- All request/response schemas
- Query parameters and path parameters

### Quick reference — main route groups

| Group      | Routes                        |
|------------|-------------------------------|
| Auth       | login, me                     |
| Assets     | CRUD + search by machine ID   |
| Check-ins  | CRUD (GPS + photo + notes)    |
| Customers  | CRUD with contacts            |
| Locations  | CRUD + child rooms            |
| Rooms      | CRUD (nested under locations) |
| Users      | CRUD                          |
| Geofences  | CRUD + user assignment (service areas) |
| Visits     | CRUD + stats                  |
| Stats      | Dashboard aggregates          |
| Exports    | Assets CSV, Check-ins CSV, Service Summary CSV |
| Uploads    | Icon + Photo upload           |
| OCR        | Extract machine ID from sticker image |
| Settings   | Dynamic CRUD for 6 entity types |
| Proximity  | Find assets near GPS point    |
| Activity   | Activity feed                 |

## Working with both projects

### Adding a feature to BOTH web + Android
1. Design the endpoint in `openapi.yaml` first
2. Implement in `server.py`
3. Run backend tests
4. Implement Android client code + run Android tests

### Adding a feature to web ONLY
1. Add endpoint to `server.py`
2. Update `openapi.yaml`
3. Android app ignores the new endpoint — no changes needed

### Adding a feature to Android ONLY
1. Plan the endpoint in `openapi.yaml` (even if backend isn't ready)
2. Build Android client against the planned contract
3. Backend implements later

## Running

```bash
# Web app
cd ~/projects/canteen-asset-tracker
./start.sh                          # production
# or: uvicorn server:app --reload   # development

# Tests
cd ~/projects/canteen-asset-tracker
python -m pytest tests/ -v

# Android
cd ~/projects/canteen-asset-tracker-android
./gradlew assembleRelease           # build APK
./gradlew test                      # run unit tests
```

## Recent Changes (May 2026 — v3)

- **v3 admin separation** — Admin functionality extracted to separate project `~/projects/canteen-admin-server/` (port 8900). Main `server.py` reduced from 3005→1946 lines by removing admin routes. Main `index.html` reduced from 7645→4228 lines by removing admin UI panels. Admin app live at https://admin.canteen.ourpad.casa.
- **EXIF GPS extraction** — `/api/ocr` and `/api/upload/photo` now extract GPS coordinates from image EXIF data server-side (PIL getexif()) and return `{exif_gps: {lat, lng}}` alongside results. Raw file bytes preserved — no re-compression, no EXIF stripping.
- **OCR fix** — Now extracts only the last 5 digits from Connect ID (e.g., `05912-095330` → `95330`). Added `raw_match` field to response for debugging.
- **Barcode scanner fix** — Switched from `decodeFromVideoElement` to `decodeFromVideoDevice` with 1D-only format hints (Code 128, Code 39, EAN/UPC, ITF, Codabar). Added `TRY_HARDER` hint and 50ms scan interval for fast, reliable detection.
- **Auto check-in** — New assets automatically check in with GPS on creation (all three entry modes: barcode, OCR, manual). Best-effort: silently skips if GPS unavailable.
- **Service stability** — Added NPMPlus reverse proxy. Service runs as background process on port 8901.
