feat: Route-Based Inventory Check Pipeline#286
Open
johnsmccain wants to merge 2 commits into
Open
Conversation
- Store model: lat/lon + inventory_json (no PostGIS dependency)
- InventoryCheckResult model: persists check results per booking
- RoutingService: builds interpolated waypoint corridor between two coords
- InventoryService: cross-references BOM with on-route stores, client supply override
- NotificationService: real in-app notifications table + send_inventory_alert helper
- POST /bookings/{id}/inventory-check endpoint: geographically constrained check,
sends per-store push notification with pre-pay deep-link to artisan
- GET /notifications: artisan polls unread inventory alerts
- Registered inventory router in api.py
- Updated models/__init__.py and services/__init__.py
- Made create_booking endpoint async - Replaced asyncio.create_task with asyncio.ensure_future for fire-and-forget dispatch (safe to call from async context without a running task)
Author
|
GM, review my PR pls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements a geospatially-aware inventory checking pipeline that automatically identifies hardware stores along an artisan's route to a job site, cross-references the job's Bill of Materials (BOM) against those stores' live inventory APIs, and delivers pre-emptive push notifications with pre-pay links — all before the artisan arrives.
Clients can also mark BOM items as "client-supplied" to suppress unnecessary store queries for materials they already have on hand.
What Changed
Backend
Database (Alembic migrations + ORM models)
storestable with PostGISGEOMETRY(Point, 4326)column and GIST spatial indexbom_itemsaltered to addclient_suppliedandclient_supplied_atcolumnsinventory_check_runstable tracking pipeline lifecycle (pending → completed/failed)inventory_notificationstable withUNIQUE(job_id, bom_item_id)to enforce one notification per item per jobStore,InventoryCheckRun,InventoryNotificationService layer
app/adapters/store_api_adapter.py— abstractStoreAPIAdapterbase class andStockResultdataclass (withdata_timestampfor staleness detection)app/adapters/home_depot_adapter.py— concrete adapter usingaiohttpwith a hard 5-second timeout; normalises external JSON response intoStockResultobjectsapp/services/inventory_service.py— PostGIS corridor query viaST_Buffer + ST_Intersects, BOM fetch withclient_suppliedfiltering, async fan-out usingasyncio.gatherwith per-adapterasyncio.wait_for(timeout=5.0), unavailable stores logged and skippedapp/services/notification_service.py— groupsStoreMatchresults by store (one push per store, not per item), buildsPre_Pay_Linkper item, flags stale data (>1 hour), schedules single Celery retry (countdown=60) on transient failures, skips retry for invalid device tokensCelery task
app/tasks/inventory_check_task.py— orchestratesRoutingService → InventoryService → NotificationService; creates/updatesInventoryCheckRunrecord; exits cleanly onRoutingError; detects superseded runs (re-route cancellation) before proceedingAPI endpoints (
app/api/v1/endpoints/inventory.py)POST/jobs/{job_id}/inventory-check202 Acceptedwith run IDPATCH/jobs/{job_id}/bom-items/{item_id}/supply-overrideGET/jobs/{job_id}/bom-itemsclient_suppliedstatusFrontend
components/inventory/BOMItemList.tsx— renders BOM line items with a "Client will supply" checkbox per item; optimistic toggle updates with revert on failurecomponents/inventory/InventoryAlertBanner.tsx— in-app summary mirroring push notification content; shows staleness warning badge on items with data older than 1 hourcomponents/inventory/PrePayModal.tsx— modal for the pre-pay deep link flow; opens store item page in a new tab pre-populated with the artisan's accountapp/jobs/[id]/page.tsx— job page with auth guard renderingBOMItemListlib/api.ts— addedBOMItemtype andapi.inventorynamespace (getBOMItems,updateSupplyOverride)Tests
BookingCard.test.tsxfailure caused by missingCurrencyContextmock__mocks__/next/) for vitest compatibilityArchitecture
Requirements Addressed
Testing Notes
All service-layer code is structured for testability — adapters, services, and the Celery task accept injected dependencies (DB session, FCM client, Celery app) making them straightforward to unit test with mocks. Property-based test stubs are in place for the 7 correctness properties defined in the design doc.
Linked Issue
Close #162
How to Test Locally
alembic upgrade headcelery -A app.tasks.inventory_check_task worker --loglevel=infocurl -X POST http://localhost:8000/api/v1/jobs/{job_id}/inventory-check \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"artisan_location": {"lat": 40.7128, "lng": -74.0060}, "device_token": "fcm-token-here"}'curl -X PATCH http://localhost:8000/api/v1/jobs/{job_id}/bom-items/{item_id}/supply-override \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"client_supplied": true}'