Architecture specification, API design, Claude integration patterns, demo mode strategy, and phased build order for IdeaForge development.
533 lines
19 KiB
Markdown
533 lines
19 KiB
Markdown
# implementation plan
|
|
|
|
Detailed architecture and build plan for IdeaForge.
|
|
|
|
## decisions
|
|
|
|
- **Backend:** Python 3.11+, FastAPI, async SQLAlchemy, Pydantic, structlog, Anthropic SDK
|
|
- **Frontend:** Flutter 3.27+, Riverpod 3.1+ (generator syntax), GoRouter, dio, fl_chart
|
|
- **Database:** SQLite (file-based, persistent Docker volume)
|
|
- **Relationship to idea_manager:** Fresh codebase with core code ported (not fork, not dependency)
|
|
- **Authentication:** None for v1 — demo mode uses middleware protection
|
|
- **Deployment:** Single Docker container (FastAPI serves Flutter Web build as static files)
|
|
|
|
## relationship to idea_manager
|
|
|
|
**Decision: Port, don't fork or import.**
|
|
|
|
- Fork implies ongoing upstream sync with a 65KB CLI and features irrelevant to IdeaForge
|
|
- Import via git URL creates tight coupling and makes extension painful
|
|
- Monorepo conflates two different audiences (personal tool vs portfolio showcase)
|
|
|
|
**What gets ported:**
|
|
|
|
- SQLAlchemy models (Idea, EvaluationMetric, IdeaEvaluation, Tag, IdeaTag, IdeaDetail)
|
|
- Base + TimestampMixin
|
|
- Pydantic schemas (create/update/response for all models)
|
|
- Service layer (IdeaService, EvaluationService, MetricService, TagService, DetailService)
|
|
- Score calculation utilities
|
|
- Exception hierarchy (renamed to IdeaForgeError)
|
|
- Seed metrics (8 canonical metrics with scoring guides)
|
|
- Evaluation prompt construction (from idea_service.get_evaluation_prompt)
|
|
|
|
**What does NOT get ported:**
|
|
|
|
- CLI (Typer) — IdeaForge is API-only
|
|
- Dual async/sync session pattern — async only
|
|
- GenerationPrompt model and prompt_service — IdeaForge evaluates, not generates
|
|
- Export templates — not needed
|
|
- Expansion prompts — not needed
|
|
|
|
**Changes during porting:**
|
|
|
|
- Package name: `ideaforge` (not `idea_manager`)
|
|
- Base exception: `IdeaForgeError` (not `IdeaManagerError`)
|
|
- Config env prefix: `IDEAFORGE_` (not `IDEA_MANAGER_`)
|
|
- All services async-only (drop sync wrappers)
|
|
- New modules: `ai/`, middleware for demo mode, CORS
|
|
|
|
## backend architecture
|
|
|
|
### directory structure
|
|
|
|
```
|
|
backend/
|
|
├── src/ideaforge/
|
|
│ ├── __init__.py # version
|
|
│ ├── main.py # FastAPI app, CORS, exception handlers, lifespan
|
|
│ ├── config.py # Pydantic Settings (IDEAFORGE_ prefix)
|
|
│ ├── database.py # Async engine + session factory
|
|
│ ├── dependencies.py # get_session, get_ai_client, get_demo_guard
|
|
│ ├── exceptions.py # IdeaForgeError hierarchy + AIServiceError
|
|
│ ├── logging.py # structlog configuration
|
|
│ ├── enums.py # IdeaCategory, IdeaStatus, DetailType
|
|
│ ├── utils.py # score calculation
|
|
│ ├── middleware/
|
|
│ │ ├── demo.py # DemoModeMiddleware (blocks writes)
|
|
│ │ └── rate_limit.py # RateLimitMiddleware (per-IP, AI endpoint)
|
|
│ ├── models/ # SQLAlchemy models (ported)
|
|
│ │ ├── base.py # Base, TimestampMixin
|
|
│ │ ├── idea.py
|
|
│ │ ├── metric.py
|
|
│ │ ├── evaluation.py
|
|
│ │ ├── detail.py
|
|
│ │ └── tag.py
|
|
│ ├── schemas/ # Pydantic schemas (ported + new)
|
|
│ │ ├── idea.py
|
|
│ │ ├── metric.py
|
|
│ │ ├── evaluation.py
|
|
│ │ ├── detail.py
|
|
│ │ ├── tag.py
|
|
│ │ └── ai.py # AIEvaluationRequest/Response
|
|
│ ├── services/ # Business logic (ported, async-only)
|
|
│ │ ├── idea_service.py
|
|
│ │ ├── evaluation_service.py
|
|
│ │ ├── metric_service.py
|
|
│ │ ├── tag_service.py
|
|
│ │ └── detail_service.py
|
|
│ ├── ai/ # Claude API integration (new)
|
|
│ │ ├── client.py # AsyncAnthropic wrapper with retry
|
|
│ │ ├── prompts.py # build_evaluation_prompt()
|
|
│ │ └── evaluator.py # AIEvaluator orchestration
|
|
│ ├── api/ # FastAPI routers
|
|
│ │ ├── ideas.py
|
|
│ │ ├── evaluations.py
|
|
│ │ ├── metrics.py
|
|
│ │ └── tags.py
|
|
│ └── seed/
|
|
│ ├── metrics.py # 8 canonical metrics (ported)
|
|
│ └── demo_ideas.py # 18 curated ideas with pre-computed scores
|
|
├── alembic/
|
|
│ └── versions/
|
|
│ └── 001_initial.py # Full schema (fresh, not migrated from idea_manager)
|
|
├── tests/
|
|
│ ├── conftest.py
|
|
│ ├── test_api/
|
|
│ │ ├── test_ideas.py
|
|
│ │ ├── test_evaluations.py
|
|
│ │ └── test_ai_evaluation.py
|
|
│ ├── test_services/
|
|
│ │ └── test_ai_evaluator.py
|
|
│ └── test_middleware/
|
|
│ ├── test_demo_mode.py
|
|
│ └── test_rate_limit.py
|
|
├── pyproject.toml
|
|
├── alembic.ini
|
|
└── Dockerfile
|
|
```
|
|
|
|
### API endpoints
|
|
|
|
**Carried from idea_manager (read-only subset):**
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| GET | /api/ideas | List ideas with filters + pagination |
|
|
| GET | /api/ideas/ranking | Ideas ranked by aggregate scores |
|
|
| GET | /api/ideas/stats | Aggregate statistics |
|
|
| GET | /api/ideas/compare?ids=1,2,3 | Side-by-side comparison |
|
|
| GET | /api/ideas/{id} | Single idea with evaluations |
|
|
| GET | /api/ideas/{id}/evaluations | Evaluations for an idea |
|
|
| GET | /api/metrics | List evaluation metrics |
|
|
| GET | /api/tags | List all tags |
|
|
| GET | /health | Health check |
|
|
|
|
**New for IdeaForge:**
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | /api/ideas/{id}/ai-evaluate | Trigger Claude API evaluation |
|
|
| GET | /api/demo/status | Demo mode info (rate limits, counts) |
|
|
|
|
### claude API integration
|
|
|
|
**`ai/client.py`** — AsyncAnthropic wrapper with retry:
|
|
|
|
- Uses `claude-sonnet-4-20250514` model
|
|
- Retries up to 2 times on RateLimitError with exponential backoff
|
|
- Raises `AIServiceError` on persistent failure
|
|
- Logs request start, completion (with token usage), and errors
|
|
|
|
**`ai/prompts.py`** — Prompt construction:
|
|
|
|
- Ported from `idea_service.get_evaluation_prompt()` (idea_manager lines 326-409)
|
|
- Takes `Idea` and `list[EvaluationMetric]` as input
|
|
- Constructs structured prompt with idea details, all 8 metrics with scoring guides
|
|
- Requests JSON output: `{"evaluations": [{"metric_name": "", "score": N, "reasoning": ""}]}`
|
|
|
|
**`ai/evaluator.py`** — Orchestration pipeline:
|
|
|
|
1. Load idea with relationships from DB
|
|
2. Load active metrics
|
|
3. Build evaluation prompt
|
|
4. Call Claude API via client
|
|
5. Parse JSON response (handles markdown code blocks)
|
|
6. Validate metric names and clamp scores to valid ranges
|
|
7. Delete existing AI evaluations for this idea (evaluator="claude")
|
|
8. Store new evaluations
|
|
9. Return results
|
|
|
|
### demo mode
|
|
|
|
**Configuration:**
|
|
|
|
```python
|
|
class Settings(BaseSettings):
|
|
demo_mode: bool = False
|
|
demo_rate_limit_per_hour: int = 5
|
|
demo_seed_on_startup: bool = True
|
|
```
|
|
|
|
**DemoModeMiddleware:**
|
|
|
|
- Blocks POST/PUT/DELETE/PATCH on all endpoints
|
|
- Exception: allows POST to `/api/ideas/{id}/ai-evaluate` (rate-limited separately)
|
|
- Returns 403 with message: "Write operations are disabled in demo mode"
|
|
- Adds `X-Demo-Mode: true` header to all responses
|
|
|
|
**RateLimitMiddleware:**
|
|
|
|
- Applies only to POST on paths containing `/ai-evaluate`
|
|
- In-memory IP tracking with sliding window (1 hour)
|
|
- Returns 429 with `retry_after_seconds` on limit breach
|
|
- Adds `X-RateLimit-Remaining` header to responses
|
|
|
|
**Startup seeding:**
|
|
|
|
- On app startup (lifespan hook), if `demo_mode=True` and DB is empty
|
|
- Seeds 8 metrics, then 18 curated ideas with pre-computed evaluation scores
|
|
- Ideas span 5 categories with varied score profiles for visual diversity in radar charts
|
|
|
|
### data seeding
|
|
|
|
18 curated ideas across 5 categories, each with pre-computed 8-metric scores:
|
|
|
|
| Category | Count | Score profile intent |
|
|
|----------|-------|---------------------|
|
|
| saas | 4 | High revenue + market, varied feasibility |
|
|
| devtool | 3 | High feasibility + learning, moderate revenue |
|
|
| web_app | 4 | Varied complexity, strong market |
|
|
| mobile_app | 3 | High interest, moderate effort |
|
|
| game / library | 4 | High uniqueness, varied skepticism |
|
|
|
|
Each idea includes: title, description, problem_statement, proposed_solution,
|
|
target_audience, category, status (evaluated), tags, and 8 metric scores with reasoning.
|
|
|
|
## frontend architecture
|
|
|
|
### flutter dependencies (pubspec.yaml)
|
|
|
|
```yaml
|
|
dependencies:
|
|
flutter:
|
|
sdk: flutter
|
|
flutter_riverpod: ^2.6.1
|
|
riverpod_annotation: ^2.6.1
|
|
hooks_riverpod: ^2.6.1
|
|
flutter_hooks: ^0.20.5
|
|
go_router: ^14.8.1
|
|
dio: ^5.7.0
|
|
fpdart: ^1.1.0
|
|
freezed_annotation: ^2.4.4
|
|
json_annotation: ^4.9.0
|
|
fl_chart: ^0.70.2
|
|
talker: ^4.5.2
|
|
talker_dio_logger: ^4.5.2
|
|
shimmer: ^3.0.0
|
|
intl: ^0.19.0
|
|
|
|
dev_dependencies:
|
|
flutter_test:
|
|
sdk: flutter
|
|
build_runner: ^2.4.13
|
|
riverpod_generator: ^2.6.3
|
|
freezed: ^2.5.7
|
|
json_serializable: ^6.9.0
|
|
very_good_analysis: ^6.0.0
|
|
mocktail: ^1.0.4
|
|
```
|
|
|
|
### feature structure
|
|
|
|
```
|
|
frontend/lib/src/
|
|
├── core/
|
|
│ ├── theme/ # app_theme.dart, app_colors.dart
|
|
│ ├── errors/ # failures.dart (Freezed sealed class), result.dart (TaskResult)
|
|
│ ├── network/ # api_client.dart (dio), api_interceptors.dart
|
|
│ └── logging/ # talker_setup.dart
|
|
│
|
|
├── features/
|
|
│ ├── ideas/
|
|
│ │ ├── domain/ # IdeaEntity (Freezed), IdeaRepository (abstract)
|
|
│ │ ├── data/ # IdeaDto (JsonSerializable), ApiIdeaRepository
|
|
│ │ └── presentation/
|
|
│ │ ├── controllers/ # IdeasController, IdeaDetailController
|
|
│ │ ├── screens/ # IdeasScreen, IdeaDetailScreen
|
|
│ │ └── widgets/ # IdeaCard, IdeaFilterBar
|
|
│ │
|
|
│ ├── evaluation/
|
|
│ │ ├── domain/ # EvaluationEntity, MetricScoreEntity
|
|
│ │ ├── data/ # EvaluationDto, ApiEvaluationRepository
|
|
│ │ └── presentation/
|
|
│ │ ├── controllers/ # AiEvaluationController
|
|
│ │ ├── screens/ # EvaluationScreen
|
|
│ │ └── widgets/ # ScoreRadarChart, MetricScoreCard, MetricScoreList
|
|
│ │
|
|
│ ├── comparison/
|
|
│ │ ├── domain/ # ComparisonEntity
|
|
│ │ ├── data/ # ComparisonDto
|
|
│ │ └── presentation/
|
|
│ │ ├── controllers/ # ComparisonController
|
|
│ │ ├── screens/ # ComparisonScreen
|
|
│ │ └── widgets/ # ComparisonRadarChart, ComparisonTable
|
|
│ │
|
|
│ └── dashboard/
|
|
│ └── presentation/
|
|
│ ├── controllers/ # DashboardController
|
|
│ ├── screens/ # DashboardScreen
|
|
│ └── widgets/ # StatsSummaryCard, TopIdeasList, CategoryChart
|
|
│
|
|
└── shared/
|
|
└── widgets/ # AppScaffold, ErrorView, LoadingView, EmptyView, TagChip
|
|
```
|
|
|
|
### navigation (GoRouter)
|
|
|
|
```
|
|
/dashboard → DashboardScreen (home)
|
|
/ideas → IdeasScreen (list with filters)
|
|
/ideas/:id → IdeaDetailScreen (detail + radar chart)
|
|
/ideas/:id/evaluate → EvaluationScreen (AI eval trigger)
|
|
/compare?ids=1,2,3 → ComparisonScreen (side-by-side)
|
|
```
|
|
|
|
ShellRoute wraps all screens in AppScaffold with:
|
|
- Sidebar (desktop): Dashboard, Ideas, Compare
|
|
- Bottom nav (mobile): same 3 items
|
|
|
|
### key screens
|
|
|
|
**Dashboard (`/dashboard`):**
|
|
- Stats cards row: total ideas, evaluated count, average score
|
|
- Score distribution chart (high/medium/low)
|
|
- Top 10 ranked ideas list
|
|
- Category breakdown bar chart
|
|
|
|
**Ideas list (`/ideas`):**
|
|
- Search bar + filter chips (category, status, tags)
|
|
- Sort dropdown (score, date, name)
|
|
- Scrollable card list with pagination
|
|
- Multi-select for comparison (floating compare bar appears)
|
|
|
|
**Idea detail (`/ideas/:id`):**
|
|
- Title, category badge, status, tags
|
|
- Description, problem statement, proposed solution, target audience
|
|
- Radar chart (8 metrics, fl_chart RadarChart)
|
|
- Metric score cards with progress bars and reasoning text
|
|
- "AI Evaluate" button (shows rate limit remaining in demo mode)
|
|
|
|
**Evaluation (`/ideas/:id/evaluate`):**
|
|
- Confirmation card explaining the process
|
|
- "Start AI Evaluation" button
|
|
- Loading state: shimmer placeholders for 8 metrics, "Evaluating with Claude..." text
|
|
- Success state: animated radar chart + scores + aggregate
|
|
- Error state: rate limit countdown or retry button
|
|
|
|
**Comparison (`/compare?ids=1,2,3`):**
|
|
- Overlapping radar charts (one colour per idea, up to 5)
|
|
- Score comparison table (metrics as rows, ideas as columns)
|
|
- Legend with idea titles
|
|
|
|
### theming
|
|
|
|
Dark-first design matching portfolio site:
|
|
|
|
```dart
|
|
// Dark theme colours
|
|
darkBackground: Color(0xFF0A0A0A) // near-black
|
|
darkSurface: Color(0xFF141414) // slightly lighter
|
|
darkSurfaceVariant: Color(0xFF1E1E1E) // cards
|
|
darkOnSurface: Color(0xFFE5E5E5) // text
|
|
darkPrimary: Color(0xFF6366F1) // indigo-500
|
|
darkOutline: Color(0xFF333333) // borders
|
|
|
|
// Light theme colours
|
|
lightBackground: Color(0xFFFAFAFA)
|
|
lightSurface: Color(0xFFFFFFFF)
|
|
lightPrimary: Color(0xFF4F46E5) // indigo-600
|
|
|
|
// Semantic
|
|
success: Color(0xFF22C55E) // green-500 (score >= 7)
|
|
warning: Color(0xFFF59E0B) // amber-500 (score 5-6.9)
|
|
error: Color(0xFFEF4444) // red-500 (score < 5)
|
|
```
|
|
|
|
Font: Inter (matching portfolio site).
|
|
|
|
### AI evaluation UX flow
|
|
|
|
1. User on idea detail → sees "AI Evaluate" button
|
|
2. Taps → navigates to `/ideas/:id/evaluate`
|
|
3. Confirmation card → "Start AI Evaluation" button
|
|
4. Loading: shimmer placeholders + "Evaluating with Claude..." (5-10s)
|
|
5. Success: animated radar chart draw + metric scores slide in + aggregate prominently displayed
|
|
6. Error (429): "Rate limit exceeded" with countdown timer
|
|
7. Error (500): "Evaluation failed" with retry button
|
|
|
|
State machine: `null` → `AsyncLoading` → `AsyncData<scores>` or `AsyncError<Failure>`
|
|
|
|
### error handling
|
|
|
|
```dart
|
|
// Failure types (Freezed sealed class)
|
|
ServerFailure(message, statusCode?)
|
|
NetworkFailure(message?)
|
|
RateLimitFailure(message, retryAfterSeconds)
|
|
NotFoundFailure(message)
|
|
DemoRestrictionFailure(message)
|
|
UnknownFailure(message?)
|
|
```
|
|
|
|
Dio interceptor maps HTTP status codes to Failure types:
|
|
- 404 → NotFoundFailure
|
|
- 403 → DemoRestrictionFailure
|
|
- 429 → RateLimitFailure (with retry_after_seconds)
|
|
- 5xx → ServerFailure
|
|
|
|
Retry interceptor: 2 retries on 502/503/504 and connection timeouts only.
|
|
|
|
## deployment
|
|
|
|
### topology
|
|
|
|
```
|
|
Internet
|
|
│
|
|
┌────────▼────────┐
|
|
│ Docker Container │
|
|
│ (Fly.io/Railway) │
|
|
│ │
|
|
│ FastAPI serves: │
|
|
│ - /api/* routes │
|
|
│ - Flutter Web │
|
|
│ static files │
|
|
│ │
|
|
│ ┌──────────────┐ │
|
|
│ │ SQLite volume │ │
|
|
│ └──────────────┘ │
|
|
└────────────────────┘
|
|
```
|
|
|
|
Single container. FastAPI serves both the API and the Flutter Web build as static files
|
|
via `StaticFiles` mount. No CORS issues, single deployment target.
|
|
|
|
### build pipeline
|
|
|
|
1. `cd frontend/ && flutter build web --release --dart-define=API_BASE_URL=/api`
|
|
2. Copy `frontend/build/web/` to `backend/frontend_build/`
|
|
3. `docker build -t ideaforge backend/`
|
|
4. Deploy container with persistent SQLite volume
|
|
|
|
### environment variables
|
|
|
|
| Variable | Default | Required | Description |
|
|
|----------|---------|----------|-------------|
|
|
| IDEAFORGE_DATABASE_URL | sqlite+aiosqlite:///data/ideas.db | No | DB path |
|
|
| IDEAFORGE_HOST | 127.0.0.1 | No | API host |
|
|
| IDEAFORGE_PORT | 8000 | No | API port |
|
|
| IDEAFORGE_ANTHROPIC_API_KEY | — | Yes (for AI eval) | Claude API key |
|
|
| IDEAFORGE_AI_MODEL | claude-sonnet-4-20250514 | No | Model ID |
|
|
| IDEAFORGE_DEMO_MODE | false | No | Enable demo mode |
|
|
| IDEAFORGE_DEMO_RATE_LIMIT_PER_HOUR | 5 | No | AI eval rate limit |
|
|
| IDEAFORGE_LOG_LEVEL | INFO | No | Log level |
|
|
| IDEAFORGE_LOG_FORMAT | json | No | json or console |
|
|
|
|
## portfolio site integration
|
|
|
|
**Decision: Link out to dedicated subdomain (ideaforge.kschappell.com), not embedded.**
|
|
|
|
IdeaForge is a Flutter Web app — embedding in an iframe creates cross-origin issues,
|
|
doubles load time, and looks unprofessional. A dedicated subdomain gives the project its
|
|
own identity.
|
|
|
|
Portfolio site `/projects/ideaforge` page will include:
|
|
- Architecture diagram
|
|
- Screenshot gallery (dashboard, radar chart, AI evaluation, comparison)
|
|
- "View Live Demo" primary CTA button
|
|
- Technology breakdown
|
|
|
|
## build order
|
|
|
|
### phase 1 — backend foundation (8-10 commits)
|
|
|
|
1. Scaffold `backend/` with pyproject.toml, alembic.ini, config
|
|
2. Port models from idea_manager
|
|
3. Port schemas + new AI schemas
|
|
4. Create initial Alembic migration
|
|
5. Port services (async-only)
|
|
6. Port utilities (score calculation)
|
|
7. Create API routers
|
|
8. Wire up FastAPI app (main.py, dependencies, exception handlers, CORS)
|
|
|
|
### phase 2 — AI integration (4-5 commits)
|
|
|
|
1. `ai/client.py` (Anthropic SDK wrapper with retry)
|
|
2. `ai/prompts.py` (ported from idea_service)
|
|
3. `ai/evaluator.py` (orchestration pipeline)
|
|
4. `POST /api/ideas/{id}/ai-evaluate` endpoint
|
|
5. Tests for AI module (mocked Anthropic client)
|
|
|
|
### phase 3 — demo mode (3-4 commits)
|
|
|
|
1. Demo mode middleware (write protection)
|
|
2. Rate limit middleware
|
|
3. Demo seed data (18 curated ideas)
|
|
4. Startup seeding in lifespan hook
|
|
|
|
### phase 4 — frontend foundation (6-8 commits)
|
|
|
|
1. Scaffold Flutter project, pubspec.yaml, analysis_options
|
|
2. Core layer (theme, errors, network, logging)
|
|
3. Ideas feature domain + data layers
|
|
4. Ideas feature presentation (list screen, filter bar, cards)
|
|
5. Idea detail screen with basic layout
|
|
6. GoRouter setup + AppScaffold with navigation
|
|
|
|
### phase 5 — evaluation & charts (5-6 commits)
|
|
|
|
1. Evaluation feature domain + data layers
|
|
2. Score radar chart widget (fl_chart)
|
|
3. Metric score card / list widgets
|
|
4. AI evaluation controller + evaluation screen
|
|
5. Loading/error/success states
|
|
|
|
### phase 6 — dashboard & comparison (4-5 commits)
|
|
|
|
1. Dashboard feature (stats, top ideas, category chart)
|
|
2. Comparison feature domain + data
|
|
3. Comparison screen with overlapping radar charts
|
|
4. Comparison score table
|
|
|
|
### phase 7 — deployment & polish (3-4 commits)
|
|
|
|
1. Dockerfile and docker-compose.yml
|
|
2. Flutter web build integration with backend
|
|
3. Portfolio site project entry
|
|
4. Final polish (animations, responsive breakpoints)
|
|
|
|
## critical source files for porting
|
|
|
|
| Source (idea_manager) | Target (ideaforge) | Notes |
|
|
|---|---|---|
|
|
| `services/idea_service.py` | `services/idea_service.py` | Drop sync, drop CLI helpers |
|
|
| `services/idea_service.py:326-409` | `ai/prompts.py` | Extract prompt builder |
|
|
| `seed/default_metrics.py` | `seed/metrics.py` | 8 metrics verbatim |
|
|
| `models/*.py` | `models/*.py` | Rename base exception |
|
|
| `schemas/*.py` | `schemas/*.py` | Add AI schemas |
|
|
| `exceptions.py` | `exceptions.py` | Rename to IdeaForgeError |
|
|
| `utils.py` | `utils.py` | Score calculation |
|
|
| `config.py` | `config.py` | Change prefix |
|
|
| `database.py` | `database.py` | Async only |
|
|
| `dependencies.py` | `dependencies.py` | Add AI client dependency |
|