Proactive Assistant is a FastAPI application that predicts likely ride and food actions before the user explicitly requests them. It combines:
- Persistent behavioral memory in SQLite
- Authenticated browser automation with Playwright
- Deterministic trigger evaluation running in the background
- Live data hydration for Uber and Swiggy
- A single-page frontend for confirmation, feedback, and inspection
The system is designed around two product surfaces:
- Ride Assistant: predicts likely next rides, validates them against live pricing and ETA data, and exposes a one-tap confirmation flow
- Food Assistant: predicts likely next orders, checks live restaurant availability and delivery timing, and presents alternatives when the preferred option is weak or unavailable
- Python 3.10 or newer
- Chromium installed through Playwright
- Network access for third-party pages and public APIs
cd proactive_assistant
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
playwright install chromiumCreate .env in the project root:
GROQ_API_KEY=
GROQ_MODEL=openai/gpt-oss-120b
GEOCODING_API=
PORT=8000Notes:
GROQ_API_KEYis optional. Without it, the app falls back to deterministic explanations and ranking.GEOCODING_APIis used for geocoding support.PORTis currently not consumed byrun.py; local dev starts on8001unless you run uvicorn manually.
Using the provided entry point:
python run.pyThis starts the server on:
http://localhost:8001
Equivalent uvicorn invocation:
uvicorn proactive_assistant_app.app:app --host 0.0.0.0 --port 8001 --reload- Open
http://localhost:8001/login - Authenticate into the application shell
- Connect Uber from the rides flow
- Connect Swiggy from the food flow
- Run history sync
- Let pattern extraction populate behavioral memory
- Reload the main UI and inspect
/rides,/food, and/api/trigger/log
Loom: https://www.loom.com/share/e2c98321cf0f48b69455ce2a091ebe58
The application does not ask the user to export cookies or manually inject credentials. Instead, account connection is performed inside a remote Playwright browser session:
- Uber login is opened in a mobile-emulated Chromium context
- Swiggy login is opened in a desktop Chromium context
- The frontend browser viewer streams screenshots from the headless page
- User clicks and keystrokes are relayed back into Playwright over API calls
- Once login is detected, the authenticated session is persisted locally and reused for subsequent scraping
Uber persists cookies to proactive_assistant_app/browser_session/cookies.json.
Swiggy persists Playwright storage state to proactive_assistant_app/browser_session/swiggy_storage_state.json.
At application startup:
- SQLite schema initialization runs
- Pattern extraction is launched in a background thread
- A persistent trigger watcher starts
The trigger watcher evaluates patterns every 60 seconds and decides whether to emit a proactive suggestion.
The current implementation is primarily deterministic:
- ride patterns are derived from historical departure windows and destination clusters
- food patterns are derived from historical order windows, cuisine frequency, and restaurant preference scores
- live data is fetched only after the system has a candidate worth showing
Groq is optional and is used in two narrow places:
- concise natural-language reason generation
- ranking current ride candidates when an API key is configured
If no Groq key is provided, the system remains functional and falls back to deterministic ranking.
- FastAPI application:
proactive_assistant_app/app.py - Entry point:
run.py - Default local dev port:
8001
- SQLite database:
proactive_assistant_app/assistant.db - WAL mode enabled
- Stores raw history, extracted patterns, trigger logs, suggestions, feedback, scraper cache, scraper snapshots, platform connection metadata, and app settings
app.py: API surface, startup lifecycle, authentication cookie handling, live context aggregation, page servingdatabase.py: persistence layer and behavioral memory primitivespattern_engine.py: extraction of ride and food patterns plus feedback reweightingtrigger_watcher.py: background loop that decides when to surface suggestionssuggestion_builder.py: converts triggers into actionable suggestions and hydrates them with live datauber_client.py: Uber Playwright login, history scraping, live estimate scraping, deeplink generationfood_client.py: Swiggy Playwright login, order-history scraping, top-restaurant scraping, live restaurant matchingdata_fetcher.py: timeouts, fallback execution, cache reuse, stale-data handling, scraper health trackingsuggestion_engine.py: legacy / auxiliary Groq-backed ride ranking path still used by current ride suggestion APIsreason_generator.py: optional one-line explanation generation with deterministic fallback
- Static SPA served from
proactive_assistant_app/static/ - Main files:
index.htmlapp.jsstyles.csslogin.html
The frontend is route-driven and exposes:
//rides/food
The SQLite schema is not just a storage layer for scraped history. It is the system's memory.
ridesorders
These store normalized Uber ride history and Swiggy order history, including timestamps, pricing, coordinates, labels, and metadata required for downstream inference.
departure_patternsdestination_clustersorder_patternsrestaurant_patternscuisine_by_day
These tables represent the learned behavioral model used by the trigger engine.
trigger_logsuggestionsdismissed_suggestionsconfirmed_suggestionspattern_feedback
These tables allow the system to:
- know when it last triggered
- know what it showed
- know whether the user confirmed, dismissed, edited, or ignored the suggestion
- suppress patterns that are repeatedly rejected
scraper_cachescraper_snapshotsplatform_connectionsapp_settings
These support session persistence, stale fallback data, scraper health reporting, and connection state.
Trigger evaluation lives in trigger_watcher.py.
- The watcher runs every
60 seconds
Ride suggestions are considered when:
- the current time is within a 20-minute tolerance of a learned departure window
- the pattern confidence passes threshold
- the user has not already booked a ride in that same window today
- the pattern is not suppressed due to repeated dismissals
The watcher also compares live route travel time to historical travel time:
- if live travel time exceeds historical average by 25 percent, the trigger is strengthened
- this adds
traffic_deviationas a trigger reason - it can shift the recommended departure earlier
Ride annoyance controls:
- base cooldown:
45 minutes - confidence threshold increases after repeated dismissals
- 4 dismissals in the recent window suppress the pattern
- pending suggestions expire after 10 minutes and count as ignored feedback
Food suggestions are considered when:
- the current time is within a 15-minute tolerance of a learned ordering window
- the pattern confidence passes threshold
- the user has not already ordered from that restaurant in the same window today
- the pattern is not suppressed due to dismissals
The watcher also compares current delivery ETA to historical delivery time:
- if current ETA exceeds historical ETA by 30 percent, the trigger is strengthened
- this adds
delivery_delayas a trigger reason
Food annoyance controls:
- base cooldown:
30 minutes - repeated dismissals increase the threshold
- 4 dismissals suppress the pattern
Pattern extraction lives in pattern_engine.py and runs:
- at startup
- after ride history sync
- after food history sync
The engine:
- clusters destinations by label similarity and geographic proximity
- converts departure times into 15-minute bins
- groups rides by weekday, hour bin, destination cluster, platform, and ride type
- stores only patterns with enough support to be meaningful
The engine:
- groups orders by weekday and 15-minute order windows
- tracks cuisine frequency per day
- builds restaurant preference scores using frequency and recency
- stores recent, high-signal restaurant preferences for live matching
When the user confirms or dismisses a suggestion, the engine updates pattern state:
- confirmations increase frequency and refresh recency
- dismissals increase dismissal count
- repeated dismissals suppress the pattern
- confirmed edits can create new pattern variants
The system does not trust scraped live data as always available.
data_fetcher.py wraps critical live data calls with:
- an 8-second primary timeout
- a 5-second fallback timeout
- scraper cache writes
- stale-cache reuse
- scraper health tracking
If both primary and fallback fail:
- the most recent cached payload is returned as a
CachedResultif available - otherwise the result becomes
DataUnavailable
For rides, the system fetches:
- live estimates from
m.uber.com - network responses containing fare and product data
- DOM candidates as a secondary extraction channel
If precise live data is missing, the suggestion still survives with:
- historical destination confidence
- deeplink generation
- explicit fallback messaging
For food, the system fetches:
- top restaurants from Swiggy internal listing endpoints
- live restaurant matching for the preferred restaurant and item
- delivery time and deeplink data
If the preferred restaurant is weak or unavailable:
- the builder switches to historical restaurant alternatives
The UI is designed to degrade gracefully:
- geolocation falls back to IP lookup
- weather, air quality, and reverse geocoding reuse cached values when live lookups fail
- food and ride panels render non-blocking placeholders while background fetches resolve
- cached or stale scraper results are still surfaced when they are better than showing nothing
Uber automation in uber_client.py has two distinct modes:
- login/browser-viewer mode
- scraping/live-quote mode
Login flow:
- launches headless Chromium with mobile emulation
- streams screenshots to the frontend
- relays click, type, and key events from the UI
- tracks popup windows for OAuth flows
- treats login as complete when redirected away from auth pages or when session cookies such as
sid/csidare present
History sync:
- opens
https://riders.uber.com/trips - captures CSRF tokens from the page's own requests
- replays Uber's internal GraphQL
Activitiesquery in-page - paginates through
nextPageToken - augments records with trip-detail extraction when needed
Live quotes:
- opens
https://m.uber.com/looking?... - listens to fare/product network responses
- parses DOM price cards as a secondary extraction channel
Swiggy automation in food_client.py also has two modes:
- login/browser-viewer mode
- authenticated same-origin data fetch mode
Login flow:
- launches headless Chromium in desktop mode
- persists Playwright
storage_state - verifies login by checking account URLs or account/profile DOM markers
History sync:
- reuses the authenticated browser context
- loads the orders page
- executes same-origin
fetchrequests inside the page for/dapi/order/all - paginates order history
- falls back to DOM scraping if the internal API path fails
Restaurant discovery:
- fetches
/dapi/restaurants/list/v5from within the authenticated page context - falls back to homepage DOM scraping if the endpoint fails
The app uses a simple application-level auth gate for access to the UI:
- manual login via
/api/auth/login - optional Google Sign-In via
/api/auth/google - session stored in an HTTP-only cookie
Google Sign-In is optional. If GOOGLE_CLIENT_ID is not configured, the rest of the application still works.
This auth layer controls access to the SPA. Platform-specific authentication for Uber and Swiggy is separate and managed via Playwright sessions.
GET /loginGET /GET /ridesGET /foodGET /api/auth/statusPOST /api/auth/loginGET /api/auth/google/configPOST /api/auth/googlePOST /api/auth/logout
GET /api/context/liveGET /api/geocode
GET /api/uber/statusPOST /api/uber/loginGET /api/uber/screenshotPOST /api/uber/clickPOST /api/uber/typePOST /api/uber/keyPOST /api/uber/finish-loginPOST /api/uber/sync-historyGET /api/uber/history
GET /api/food/statusPOST /api/food/login/{provider}GET /api/food/screenshotPOST /api/food/clickPOST /api/food/typePOST /api/food/keyPOST /api/food/finish-login/{provider}POST /api/food/sync-historyGET /api/food/historyGET /api/food/top-restaurants
GET /api/history/syncGET /api/patterns/summaryGET /api/ride/patternsGET /api/food/patternsGET /api/suggestions/currentGET /api/ride/suggestionGET /api/food/suggestionPOST /api/suggestions/{suggestion_id}/confirmPOST /api/suggestions/{suggestion_id}/dismissPOST /api/ride/confirmPOST /api/ride/dismissPOST /api/food/confirmPOST /api/food/dismissGET /api/trigger/log
GET /api/healthGET /healthWS /ws/suggestions
Playwright session files are stored under:
proactive_assistant_app/browser_session/
These files contain authenticated browser state and should be treated as sensitive local credentials.
SQLite database path:
proactive_assistant_app/assistant.db
To reset local state, remove the database and browser session artifacts, or use the provided cleanup script:
./clear_data.shUber history scrape debug output is written to:
proactive_assistant_app/scrape_debug/uber_history_last.json
Use:
/health/api/health/api/trigger/log/api/patterns/summary
to inspect scraper health, trigger behavior, and extracted memory state.
The repository includes targeted tests under tests/, for example:
tests/test_live_uber_estimates.pytests/test_suggestion_builder_geocoding.pytests/test_ola_client.pytests/test_uber_history_quality.py
Run them with:
pytest- The application is optimized for local single-user operation
- Browser automation depends on third-party site structure and internal endpoints
- Some environment variables documented in
.envare optional rather than mandatory run.pypins development startup to port8001- The UI auth cookie is application-level only; it is not a substitute for Uber or Swiggy authentication
This codebase is not a static demo. It is a stateful local system with:
- authenticated platform sessions
- persistent behavioral memory
- background trigger evaluation
- live data hydration
- explicit fallback and cache reuse
- feedback-driven suppression and reweighting
That combination is the core of the product: not just prediction, but prediction that remains operational under unreliable external integrations.