Commit Graph

59 Commits

Author SHA1 Message Date
shawn 8a4f76b56e Add related work orders to asset detail view (live extraction DB query). Add location filter to work orders list. Add Related Work Orders card in frontend. 2026-07-28 20:49:45 -04:00
shawn 96ed283163 Replace broken Location dropdown with Place (city) dropdown + Location Facility text input. Add /api/places endpoint. Add place/location_text filter params to /api/assets. Add functional_location to work orders API and frontend. 2026-07-28 20:34:03 -04:00
shawn 2d290640ca Fix asset list filters: MAKE→manufacturer, populate customers, expand search, fix assigned_to type 2026-07-28 19:25:13 -04:00
shawn 874e15c01e Add disney_group grouped park filter categories
- New disney_group query param on /api/assets (park, resort,
  office, springs, other)
- Both list and map filter dropdowns show group options above
  individual parks
- Backend uses IN queries for grouped park values
- Map view handles groups via client-side PARK_GROUP_MAP

Closes #69
2026-06-03 00:57:55 -04:00
shawn e59b0fd6b6 fix: activity log now shows machine_id/name, 164 Disney assets updated with park data, WO date range defaults to week 2026-06-01 23:47:30 -04:00
shawn 03cf4b9281 feat: Route tab overhaul + work order filters + Next Stop + Google Maps
- Added work order filter bar to Route tab matching the Work Orders tab UX:
  status, priority, work type dropdowns + date range selector
- Filter params passed to /api/workorders/today endpoint (backend updated
  with new query params: status, priority, work_type, date_range)
- Added Google Maps Navigate button on every route stop
- Added Mark Done & Next Stop button — tracks current stop index,
  marks completed stops with green check, advances to next with
  auto-prompt to open Google Maps navigation
- Animated stop progress: done stops dimmed with green ✓, current
  stop highlighted with accent border
- Updated /api/workorders/today to support date_range (week/month/all)
  and additional filter WHERE clauses
2026-06-01 13:10:37 -04:00
shawn 2d232941da Fix single checkin GET to include username via JOIN 2026-06-01 00:27:29 -04:00
shawn e325981b17 Fix check-in user tracking
- Backend: create_checkin now auto-resolves user_id from auth token
  when not provided in the request body
- Backend: list_checkins now JOINs users table to return username
  alongside each check-in
- Frontend: all 4 check-in functions now pass user_id from
  AppState.currentUser
- Frontend: check-in history shows 👤 username next to each entry
- Also made work order count clickable: shows WO detail modal
- Added work_orders list to MSFS asset response

Closes #62
2026-05-31 23:54:13 -04:00
shawn f1a3bb9f00 feat: revised category icons + unknown type detection
- Cleaned CATEGORY_MAP: removed dead entries (Furniture, GF Bev,
  Snack/bev, Snack/food), fixed duplicate 'Food' key, proper
  distinct icons for all 6 real categories
- Added categoryInfo() helper: 🥤/🍿/🍔/🔧/🔌/📦 for known cats,
   (red) for unmapped categories, 📦 for null
- Updated all render sites (list, map pins, map list, detail view,
  scan result, OCR result) to use categoryInfo()
- Added category icons to detail view and scan/OCR result headers
- Synced categories DB table: added Equipment/Appliances with icons,
  removed unused Unknown
- Updated seed data in both server.py and admin_server.py to match
  real asset categories
- Added category_health to /api/stats with unmapped category detection
- Added Category Health card to admin dashboard (shown only when
  unmapped categories exist)
- Updated CSS backgrounds for all real categories + .cat-unmapped
2026-05-31 00:28:03 -04:00
shawn 17b870e4cc feat: wire vision/OCR results back into asset records
- Auto-save photo_path to matched assets when photo uploaded via /api/ocr
- Auto-extract and save serial_number from OCR text to blank-serial matched assets
- Add _extract_serial_from_text helper for serial number pattern matching
- Create scripts/backfill_vision_photos.py for batch-processing unlinked photos
- Add docs/OCR_PIPELINE.md documenting the full OCR/vision pipeline
- Fix test DB schema: add connect_id, equipment_id, barcode, customer_name
- Fix OCR test assertions to match actual endpoint behavior
2026-05-29 10:03:51 -04:00
shawn b80ed4b483 feat: Ollama vision OCR via Windows PC for label photo matching
- Add persistent SSH tunnel (systemd service) to Windows PC Ollama
- Add _HAS_OLLAMA check at server startup (qwen2.5vl:3b)
- Replace Tesseract-only OCR with Ollama-first strategy
- Ollama (qwen2.5vl:3b) handles dark/complex labels Tesseract can't read
- Keurig serial 2500.0100.0025534 now correctly matched as Asset #5144
- Add ocr_source field to /api/ocr response ('ollama' or 'tesseract')
- Fix match_label_photo.py argparse bug (image_paths -> images)
- Update match_label_photo.py CLI to read vision key from Hermes config
2026-05-29 08:59:53 -04:00
shawn 8022c77b70 feat: add normalized matching for label photos → asset DB lookup
- normalize_identifier() strips dots/dashes/prefixes, keeps alphanumeric
- find_asset_by_normalized_id() searches serial_number, connect_id,
  equipment_id, barcode with normalized comparison
- /api/ocr now returns matched_assets in addition to legacy machine_id
- New /api/match-text endpoint for client-side text matching
- scripts/match_label_photo.py CLI tool for OCR + DB matching
- Vision model fixed (mimo-v2-omni at opencode.ai, was using
  truncated placeholder key)
2026-05-29 08:37:06 -04:00
shawn e10e226743 Add serial_number to barcode search
- /api/assets/search now also matches serial_number column
  so scanning a sticker with just the serial number works
2026-05-29 07:59:37 -04:00
shawn 8c8f8a261c Add 2D barcode scanning + connect_id search
- Add QR_CODE, DATA_MATRIX, PDF_417, AZTEC to barcode scanner hints
  so serial number stickers with 2D barcodes can be scanned
- Update /api/assets/search to also match connect_id so full
  Connect IDs (e.g. VM-05912-0000068454) work when scanned
- Bump SW cache v5→v6, footer v3.2→v3.3
2026-05-29 07:55:44 -04:00
shawn 4f9c0d0b75 feat: populate company/place/customer_name from MSFS extraction DB
- New script: scripts/populate_asset_company_place.py maps connect_id →
  account name (company/customer_name) + city (place) + address via
  extraction DB (msdyn_customerasset + account join)
- 5,743/7,488 assets now have company + customer_name populated
- 5,647/7,488 assets now have place (city) populated
- Fix: SELECT COALESCE(c.name, a.customer_name) so stored customer_name
  isn't overridden to NULL when customer_id FK is missing
- Bump SW v4→v5, version v3.1→v3.2 to force cache refresh
2026-05-29 07:45:01 -04:00
shawn 9ee8de8578 fix: add/find tab now lookup-only, nav fix, route techs fix
- Add/Find tab: removed check-in card and add-asset form, now pure lookup
- OCR: stop camera after capture, added GPS capture at photo time
- OCR: increased timeout from 8s to 15s to reduce 500 errors
- Nav tab: added GPS prompt + asset search when navigating directly
- Route: include all 26 techs from bookableresource table (not just Shawn)
2026-05-29 00:10:30 -04:00
shawn 1db6475f83 feat: asset naming, location extraction, and location display
- New asset naming: {MID} - {Customer} / {Address} / {Building details}
- Extracted 660 building_names from customer data (Disney resorts, hotels)
- 1,704 assets now have location data (building_name, floor, etc.)
- Location section in detail view now shows building, floor, trailer, room
- Added has_gps filter to API for map loading
- Added location_source column to track GPS origin
2026-05-28 23:58:56 -04:00
shawn ae3b114ebc feat: import Seed (mycantaloupe.com) data — 1,655 assets enriched
- Added import_seed.py — reads Machine List(8).xlsx, matches by Asset ID → machine_id
- 51 new columns on assets table (route, barcode, sales, contact info, etc.)
- seed_data table with full 62-field JSON per asset for detail enrichment
- Seed data card in frontend asset detail view
- GET /api/assets/{id} now returns result.seed and result.seed_imported_at
- GPS columns from Seed ignored per request
- 1,654 matched, 199 unmatched (Seed-only assets not in MSFS)

Refs #47
2026-05-28 23:28:08 -04:00
shawn a8158566ac feat: OCR auto-saves GPS to asset + MSFS enrichment in detail view
OCR endpoint (/api/ocr) now auto-saves EXIF GPS from the photo
to the asset's latitude/longitude fields when they are blank,
so the first photo scan at a machine permanently records its location.

GET /api/assets/{id} now includes an 'msfs' block enriched from
the merged Dynamics 365 Field Service data (merged-assets.json)
loaded at server start (1,834 matched assets). Fields shown:
equipment_id, connect_id, compass_asset_number, manufacturer,
model_text, dex_model, install_date, software_version, etc.

Frontend updates:
- New 'Field Service Data' card in asset detail view when MSFS
  data is available (manufacturer, Connect ID, install date, etc.)
- '📍 GPS saved to asset' confirmation shown after OCR auto-save
- AppState.ocrGpsSaved flag to track the auto-save event
2026-05-28 22:28:34 -04:00
shawn 7c1ceafc6b Remove auth: fall back to default admin user when no token provided (field tool mode) 2026-05-28 22:03:58 -04:00
shawn f3d176116a Major app overhaul: MSFS data import, lookup-only Find tab, OSRM nav, cleanup 2026-05-28 21:57:58 -04:00
shawn da2a23f6da Inline route optimization into canteen server (remove proxy to daily-route on port 8912)
- Added extraction DB config and _get_extraction_db() helper
- Added _status_label, _haversine, _solve_tsp (nearest-neighbor + 2-opt)
- Added GET /api/workorders/search (native)
- Added POST /api/workorders/lookup (native)
- Added GET /api/workorders/today (native, reads bookableresourcebooking)
- Added GET /api/workorders/technicians (native)
- Added POST /api/route/optimize (native TSP solver)
- Removed httpx dependency, _proxy_route, _get_route_client
- Removed all proxy endpoints
2026-05-27 18:18:24 -04:00
shawn a1022b300e Sync prod: customer_name JOIN, search debounce, parent company filter, asset detail card with map 2026-05-26 15:54:07 -04:00
shawn a305766d65 fix: show '— No report yet' for assets without dex_report_date 2026-05-23 18:46:58 -04:00
shawn 4ebe3a5076 feat: add dex_report_date to AssetCreate model + test for no_dex_days filter
- Add dex_report_date field to AssetCreate Pydantic model
- Include dex_report_date in _build_asset_insert columns/values
- Add test_list_assets_filter_no_dex_days test (11/11 pass)
2026-05-23 17:29:37 -04:00
shawn 900f912147 v5 migration: add dex_report_date and install_date columns to assets table 2026-05-23 17:26:11 -04:00
shawn a0e6f627f4 Revert "Add clear GPS endpoint + frontend button"
This reverts commit ca77a29d7d.
2026-05-22 23:32:51 -04:00
shawn ca77a29d7d Add clear GPS endpoint + frontend button
- DELETE /api/assets/{id}/gps sets lat/lng to NULL
- Detail view shows 'Clear GPS' button when asset has GPS coords
- Confirmation modal before clearing
- Tests: 204 success + 404 not found
2026-05-22 23:30:07 -04:00
shawn 95b02a1ecc fix: dynamic VALID_CATEGORIES from DB + populate categories table
- server.py: VALID_CATEGORIES now loaded from categories table at startup
  (was hardcoded to {Furniture, Appliances, Utensils, Equipment, Other})
- categories table now includes: Bev, Snack, GF Bev, GF Food,
  Snack/bev, Snack/food, Food
- Cleared GPS for MID 60006 per user request
2026-05-22 19:43:50 -04:00
shawn db6859a0f5 fix: Add Cache-Control: no-cache to static files to prevent stale HTML/image caching 2026-05-22 18:44:36 -04:00
shawn e8a918fc7b feat: add is_disney column for reliable Disney/Non-Disney filtering
- Added is_disney INTEGER DEFAULT 0 column to assets table
- Backfilled 996 Disney assets based on D- customer prefix
- Server: is_disney in AssetCreate/AssetUpdate models, INSERT/UPDATE
- Server: disney_filter query param now uses is_disney column (no JOIN needed)
- import_cantaloupe.py: sets is_disney=1 when customer starts with D-
- import_machines.py: sets is_disney=1 when customer starts with D-
- disney_classify.py: sets is_disney=1 when classifying
- Frontend: new Location Type dropdown with Disney/Non-Disney toggle
  (replaces the __disney__/__non_disney__ park dropdown pseudo-options)
2026-05-22 18:34:13 -04:00
shawn 60dfd3434a feat: add Disney/Non-Disney quick filter to dropdown panel
Backend:
- disney_filter query param on /api/assets (values: 'disney' | 'non_disney')
- Detects Disney by checking disney_park IS NOT NULL OR customer name LIKE 'D-%'
- Catches ESPN, Wide World of Sports, all Disney resorts, etc.
- Adds LEFT JOIN to customers table when disney_filter is active

Frontend:
- '🏰 Disney only' and '🏢 Non-Disney' options in Disney Park dropdown
- Active filter tag shows which is selected
- Works together with other dropdown filters
2026-05-22 18:25:54 -04:00
shawn e0412acbe3 feat: add Category, Make, Disney Park to filter panel dropdowns with backend endpoints 2026-05-22 18:19:32 -04:00
shawn d98a791c96 feat: improved asset filter UX - collapsible filter panel, customer/location cascade, status dropdown, assigned_to filter, active filter tags, result count
Backend:
- GET /api/customers — lists all customers for filter dropdown
- GET /api/locations?customer_id=X — cascading locations per customer
- GET /api/users — lists users for assigned_to filter

Frontend:
- Collapsible 'Filters' panel with Customer→Location cascade, Status, Assigned To
- Active filter tags showing what's currently filtering, removable individually
- 'Clear all filters' button to reset all at once
- Result count display ('X assets found')
- Better search placeholder showing searchable fields
- Filter count badge on the toggle button
2026-05-22 18:15:35 -04:00
shawn 35ccebb0f0 Add serial_number to search bar query 2026-05-22 18:07:42 -04:00
shawn 7394d44caf Load all assets at once (no pagination), add server-side disney_park filter 2026-05-22 17:59:33 -04:00
shawn 95b5bd26eb Add pagination (Prev/Next with counter) and X-Total-Count header 2026-05-22 17:57:24 -04:00
shawn 27f372ed49 DB schema: add disney_park column to assets, create service_entrances table
- Added disney_park TEXT DEFAULT NULL to assets table in _create_v2_tables
- Created service_entrances table with id, location_id, name, latitude,
  longitude, notes, created_at, updated_at; location_id FK to locations
  with ON DELETE CASCADE; name/lat/lon are NOT NULL
- Added v4 migration in init_db: ALTER TABLE for disney_park on existing
  DBs, schema-aware recreation of service_entrances if name column lacks
  NOT NULL constraint
- 44/45 tests pass (1 pre-existing 404 failure on stats endpoint)
2026-05-22 17:45:47 -04:00
shawn 6aa5d3d35c Add Disney park classification + service entrance API
- Auto-classifies 1,155 Disney assets into parks/resorts/offices
- New columns/table: assets.disney_park, service_entrances
- API: /api/disney/stats, /api/disney/classify, /api/assets/{id}/disney-park
- API: /api/service-entrances CRUD, /api/locations/{id}/service-entrances
- OCR strategy: ConnectID last-5, resort keyword detection, address patterns
2026-05-22 17:41:11 -04:00
shawn 25da436bba Fix: seed admin/changeme user for frontend E2E tests
The test suite login as 'admin'/'changeme' but only 'shawn' was seeded.
Adding the admin user with SHA256('changeme') hash lets all 15 Playwright
tests reach the actual app pages instead of failing at login.
2026-05-22 15:08:39 -04:00
shawn 497d1104c1 T5: Expand DNG/RAW-02 photo upload support
- Added .dng to PHOTO_ALLOWED_EXTS
- Bumped PHOTO_MAX_SIZE from 10MB to 20MB
- DNG files (~13MB from Pixel 9a) now accepted by all upload paths:
  /api/upload/photo, /api/ocr, connect_labels
- Server validates extension+size only; DNG EXIF preserved naturally
  by write_bytes(); _re_embed_exif() correctly skips non-JPEG;
  _extract_gps_from_bytes() opens DNG as TIFF via PIL (already works)

Verified: .dng upload via /api/upload/photo and /api/ocr both succeed,
file saved byte-identical.
2026-05-21 22:35:47 -04:00
shawn 43bdedde61 T1: Clean up duplicate function declarations
- Removed duplicate clearPickedPhoto() at old line ~1772
  (kept newer version at line ~3857 with pickedPhotoGps clearing)
- Removed duplicate extractGpsFromPicked() at old line ~1831
  (kept newer async version at line ~3766 that uses exifr)
- Both ocrPickedPhoto() and createAssetFromPicked() were already
  singular — they were refactored in a prior edit

Verified: no duplicate function names, JS syntax OK, no regressions
2026-05-21 22:23:30 -04:00
shawn 78dbdfd5c5 EXIF round-trip: read before upload, re-embed server-side
- Client: exifr.parse() reads full EXIF before upload, sent as exif_data
- Server: piexif re-embeds EXIF into saved JPEG after upload
- Auto-extract GPS from picked gallery photos on selection
- Removed Google Photos button (needs OAuth, not feasible)
2026-05-21 21:29:55 -04:00
shawn 58f8b67b28 auth: full session-based auth with remember-me support
- Seed user: shawn / Brett85!@ (SHA-256)
- Sessions table with expires_at column
- login endpoint supports remember_me flag (30d vs 1d expiry)
- logout endpoint invalidates server-side token
- auth middleware + /api/auth/me check session expiry
- Frontend sends remember_me in login request body
- Frontend calls /api/auth/logout on client logout
- Migration function for existing databases
2026-05-21 20:17:21 -04:00
shawn 0824075048 t_68f4f6a2: Strip admin API routes from main server.py 2026-05-21 17:27:06 -04:00
shawn 7cf566ad6d t_2f17b1b4: Add EXIF GPS extraction to upload/photo and OCR endpoints
- _extract_gps_from_bytes helper reads GPSInfo IFD via PIL get_ifd()
- Converts DMS to decimal degrees, returns {lat, lng} or None
- /api/upload/photo: reads bytes first, extracts GPS before saving
- /api/ocr: extracts GPS from raw bytes before OCR processing
- exif_gps returned alongside path/machine_id in both endpoints
- Photos without EXIF GPS omit the exif_gps key entirely
- Raw file bytes preserved (no re-compression, no EXIF stripping)
2026-05-21 17:17:38 -04:00
shawn 09d83da4e2 server OCR: accept any file extension, detect format from bytes
- Removed extension whitelist — no more 'unsupported format' errors
- Falls back to .jpg temp file for unknown/no extension
- PIL detects actual image format from file bytes (handles DNG as TIFF)
- Android browsers often strip DNG extensions when picking from gallery
- Tested: works with .dng, .jpg, no extension, and .bogus
2026-05-21 15:05:39 -04:00
shawn 0cf5dddf83 DNG/RAW photo support for gallery upload
- Server: accept .dng in OCR endpoint (PIL opens as TIFF)
- Server: bump OCR size limit to 20MB (DNG files can be large)
- Client: gallery upload tries server OCR first (with 6s timeout),
  then falls back to client-side Tesseract.js — matches 📸 capture flow
- DNG photos now work with 'Pick from Gallery'
2026-05-21 14:51:57 -04:00
shawn 622996624a T4: Connect Label upload workflow — unified photo + OCR + GPS
- Added 'Connect' mode toggle on Add Asset tab with Camera/Gallery options
- processConnectLabelPhoto(file): parallel OCR + GPS extraction
- Shows results card with editable machine_id, lat/lng, map preview
- POST /api/connect-label endpoint with photo upload + GPS + reverse geocode
- Connect Label section in Assets list (localStorage-backed recent scans)
- Handles OCR failure (manual entry) and missing EXIF GPS gracefully
2026-05-21 11:50:09 -04:00
shawn 62016bae83 Revert all UX sweep changes (T5-T11, 16 commits)
This reverts all commits from 828fd5c..bc8cc99, undoing the entire
canteen-ux-sweep board changes: Dashboard Fixes, Map Improvements,
Modal System, Navigate fixes, Empty States, Search & Filter, Reports,
Navigate Route-Finding UI, T6 cleanup, Page Transitions, Skeleton Loading.

Restores codebase to pre-UX-sweep state at d844429.
2026-05-21 07:36:58 -04:00