Already implemented on main. Analytics page has full skeleton loaders (pulse animation cards and chart placeholders) and error state banner. Batch page has loading spinner during mutation,…
Already implemented on main. types.py line 7: patent_id: str. No int() casts of patent_id found in the codebase. All acceptance criteria met. Closing.
Already implemented on main. analyzer.py analyze_single_patent() (lines 108-162) checks if PDF exists on disk; if not, looks up the cached PDF link in the database and downloads it via…
Already implemented on main. config.py line 46: serp_cache_ttl_hours = int(os.getenv("SERP_CACHE_TTL_HOURS", "24")). Used in analyzer.py line 72: ttl_hours=config.serp_cache_ttl_hours.…
Already implemented on main. config.py line 43: model = os.getenv("MODEL", "anthropic/claude-3.5-sonnet"). llm.py reads self.model = config.model (line 28) and passes it to OpenRouter.…
Already implemented on main. All three files (analyzer.py, serp_api.py, llm.py) use logging.getLogger(__name__) with no print() calls remaining. Root logger configured in config.py…
Already implemented on main. tests/test_auth.py covers all scenarios: registration (first user admin, subsequent user, duplicate email), login (valid/invalid credentials), protected routes…
Already implemented on main. slowapi rate limiter in api.py with @limiter.limit("10/minute") on login and @limiter.limit("5/minute") on register. 429 handler returns Retry-After…
Already implemented on main. database.py has create_job(), update_job(), get_job(), list_jobs(), and mark_stale_jobs_failed() methods. api.py uses these for all job operations.…
Already implemented on main. auth.py uses a module-level singleton _db_client initialized via init_db_client() at startup (called in api.py lifespan). get_db_client() returns the…