OpenAIParser now uses AsyncOpenAI — the previous sync SDK call ran directly
on the event loop (submitting to a ThreadPoolExecutor and immediately
blocking on .result() serialized it anyway). TitleParser.raw_parser and its
callers become async accordingly.
The SPA catch-all route listed dist/ on every request; snapshot it once at
startup. The module-level bangumi TTL cache is now lock-guarded — it is
written from asyncio.to_thread workers in notification paths while the event
loop reads it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh
Database:
- Add error handling and per-step version tracking in migrations
- Enable SQLite foreign keys via PRAGMA on connect
- Fix SQLAlchemy .is_(None) usage, add session.merge() for detached
- Batch commit for semantic alias merges
- Quote table/field names in fill-null-defaults SQL
- Guard against empty user data in migration
Parsers:
- TMDB: bounded LRU cache (512), asyncio.gather for parallel season
fetches, fix season regex \d -> \d+, null-safe year, fix id shadowing
- Raw parser: re.escape() for group/prefix regex, None guard on match
- OpenAI: handle Pydantic model_dump, catch ValueError
Network:
- Null-safe get_html() return
- Error handling per RSS item in mikan parser
- Progressive retry delays (5/15/45/120/300s) with specific exceptions
- Platform detection via sys.platform instead of path heuristic
- Move filter cache to instance variable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>