Opening AI search to anonymous users with IP-based rate limiting
I opened the AI Analyst feature to unauthenticated visitors on EpsteinScan. Anonymous users get 2 queries per day tracked by IP address. I also raised the Free tier limit from 5 to 10 queries per day and Pro from 25 to 50.
Adding the anonymous usage table
The existing ai_search_log table has user_id as INTEGER NOT NULL with a foreign key, so I couldn't store anonymous queries there with a null or sentinel string. I created a new anon_search_usage table with columns for IP, search date, and count. It mirrors the same upsert pattern I use for logged-in users in search_usage. Anonymous queries still get logged to ai_search_log with user_id=0 and plan='anon' as sentinels for analytics.
Gating logic
I added get_anon_search_usage() and increment_anon_search_usage() helpers in auth.py, then extended check_ai_search_allowed(user, ip) with a branch that checks the anonymous table when no user is passed. The /api/ask endpoint and the ask page rendering logic both call this function and handle the anonymous case.
Deployment timing
Dev and production both symlink to the same auth.db. The tier limits live in code, not the database. Production served the old 5/25/50 limits until I deployed the code. The additive anon_search_usage table didn't break production because the old code never read it.
UI changes
I updated ask.html to show the input and tier messaging to anonymous visitors, changed pricing.html and signup.html to reflect the new Free tier limit, and themed the sign-up links on the AI Analyst page and in chat error bubbles to use var(--accent) instead of browser default blue.