- Synced categories table: added Equipment/Appliances with icons,
removed unused Furniture/Utensils & Serveware
- Updated seed data to match real asset categories
- Added category_health query to /api/stats (finds asset categories
not in the categories lookup table)
- Added Category Health card to dashboard UI (red warning, shown
only when unmapped categories exist)
Backend (cantaloupe_sync.py):
- GET /api/admin/cantaloupe/batches/{id}/field-changes — flat list of per-field diffs
- POST /api/admin/cantaloupe/batches/{id}/apply-field-changes — apply selected changes only
- PerFieldApprovalRequest/FieldChangeItem Pydantic models
- Activity log entries for apply-field-changes
Frontend (static/index.html):
- toggleFieldReview() — loads field changes via API, shows/hides review area
- renderFieldChanges() — geotagger-style cards with checkboxes per field, grouped by machine
- applyFieldChanges() — POST selected changes with confirmation modal
- Auto-select blank-fill changes (is_blank_fill flag)
- Sticky apply bar at bottom with live selected count
- Toggle all, toggle per-asset, toggle per-field helpers
- Review Changes button in pending review card
Add detect_replacements() function that identifies when a new Asset ID
appears at the same customer + address as an existing asset not in the
current import. Uses two confidence levels:
- high: exact address match
- medium: same building_name or same street name
Stored in diff_summary under 'replacements' key for admin review UI.
- Add heuristic entries: dex_report_date, deployed, pulled_date
- Include in COMPARE_FIELDS for diff detection
- Add to approve_batch() UPDATE field_map and INSERT statement
- Add v4 DB migration for three new TEXT columns
- Datetime serialization already handled by parse_excel()
Kanban: t_3bef3c9c
- /api/assets/search must be registered before /api/assets/{asset_id}
to prevent FastAPI path param from catching 'search' as an asset_id.
Returning a 422 with array detail caused frontend to show [object Object].
- Improve error message extraction in clearAssetGps catch block.
- DELETE /api/assets/{id}/gps: clears lat/lng to NULL
- GET /api/assets/{id}: asset lookup by ID
- GET /api/assets/search?machine_id=: asset lookup by machine ID
- Export & Admin page: Clear Asset GPS card with lookup + confirmation
- Tests: 204 success + 404 not found
Same issue as canteen-asset-tracker — hardcoded category set was
{Furniture, Appliances, Utensils, Equipment, Other}, missing all
vending-machine categories (Bev, Snack, GF Bev, GF Food, etc.).
- admin_server.py: _load_categories() from DB at startup
- cantaloupe_sync.py: reads from categories table instead of
hardcoded set (was silently downgrading GF Bev→Other on sync!)
- Replace broken icon-only sidebar collapse with off-canvas hamburger menu
that slides in from left (hidden by default on <768px)
- Add sidebar overlay backdrop, auto-close on nav/logout
- Rename 'Cantaloupe Sync' to 'Import' in sidebar and 'Excel Import' in page
- Remove 'Sync Now' button and backend subprocess auto-export entirely
- Keep manual Excel file upload as the only import path
- Update empty-state text and upload handler to work without Sync Now btn
The /api/auth/me endpoint was never added after the admin server was
split from the main server. Without it, auth tokens couldn't be validated
against the admin server — the SPA catch-all would serve the frontend instead.
Covers: customers (create/read/update/delete/list), locations (crud + latlng),
rooms (create/list/filter), geofences (crud), settings categories (create/list/delete).
All 23 tests pass against the admin server. Uses UUID-based names to avoid
shared-DB collisions with the production assets.db.
The catch-all @app.get('/{full_path:path}') was defined before API routes,
causing GET requests for /api/* endpoints to be intercepted and served
the admin SPA index.html instead of the actual API response. Moving
the catch-all to after all API routes ensures proper route resolution.
- Changed subprocess from 'python' to 'sys.executable' (fixes FileNotFoundError
in systemd — venv python not in system PATH)
- Added '📤 Upload Excel' button to admin frontend sync page
- uploadSyncFile() sends file to the same /api/admin/cantaloupe/sync endpoint
(which already supported file upload — frontend just didn't expose it)
- Users can now use 'Sync Now' for auto-download or 'Upload Excel' for manual
The subprocess.run for cantaloupe export was using cwd=admin_server_dir,
which meant 'python -m cantaloupe' couldn't find the local cantaloupe module.
Changed cwd to ~/projects/cantaloupe-downloader where the module lives.
Also added scripts/cantaloupe-sync.sh — the 6h cron job wrapper script.
- Fix greedy matching where 'name' keyword matched 'Location Name' header
- Use word-subset matching instead of substring matching
- Normalize keywords (replace _ and # with spaces) to match header normalization
- Reorder heuristics: multi-word specific patterns before single-word generics
- Add 10 unit tests for mapping, diff, and Excel parsing
- Add 23 integration tests for all sync API endpoints
- All 33 tests pass
- New cantaloupe_sync.py module with complete pipeline
- Heuristic Excel column mapping (20+ keyword patterns)
- Diff engine: detects new/changed/removed/unchanged assets
- 5 API endpoints: sync, list batches, view batch, approve, reject
- cantaloupe_sync_batches table with status workflow
- Admin server port changed to 8090
- openpyxl added to requirements.txt