# 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.