Riverpod updated to 3.2+ (Dart 3.7+), deployment platform set to TBD, riverpod_generator updated to ^4.0.2.
19 KiB
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.2+ (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(notidea_manager) - Base exception:
IdeaForgeError(notIdeaManagerError) - Config env prefix:
IDEAFORGE_(notIDEA_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-20250514model - Retries up to 2 times on RateLimitError with exponential backoff
- Raises
AIServiceErroron 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
Ideaandlist[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:
- Load idea with relationships from DB
- Load active metrics
- Build evaluation prompt
- Call Claude API via client
- Parse JSON response (handles markdown code blocks)
- Validate metric names and clamp scores to valid ranges
- Delete existing AI evaluations for this idea (evaluator="claude")
- Store new evaluations
- Return results
demo mode
Configuration:
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: trueheader 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_secondson limit breach - Adds
X-RateLimit-Remainingheader to responses
Startup seeding:
- On app startup (lifespan hook), if
demo_mode=Trueand 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)
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^3.2.0
riverpod_annotation: ^4.0.1
hooks_riverpod: ^3.2.0
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: ^4.0.2
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:
// 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
- User on idea detail → sees "AI Evaluate" button
- Taps → navigates to
/ideas/:id/evaluate - Confirmation card → "Start AI Evaluation" button
- Loading: shimmer placeholders + "Evaluating with Claude..." (5-10s)
- Success: animated radar chart draw + metric scores slide in + aggregate prominently displayed
- Error (429): "Rate limit exceeded" with countdown timer
- Error (500): "Evaluation failed" with retry button
State machine: null → AsyncLoading → AsyncData<scores> or AsyncError<Failure>
error handling
// 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 │
│ (platform TBD) │
│ │
│ 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. Hosting platform to
be decided at deployment time (candidates: Fly.io, Railway, self-hosted).
build pipeline
cd frontend/ && flutter build web --release --dart-define=API_BASE_URL=/api- Copy
frontend/build/web/tobackend/frontend_build/ docker build -t ideaforge backend/- 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)
- Scaffold
backend/with pyproject.toml, alembic.ini, config - Port models from idea_manager
- Port schemas + new AI schemas
- Create initial Alembic migration
- Port services (async-only)
- Port utilities (score calculation)
- Create API routers
- Wire up FastAPI app (main.py, dependencies, exception handlers, CORS)
phase 2 — AI integration (4-5 commits)
ai/client.py(Anthropic SDK wrapper with retry)ai/prompts.py(ported from idea_service)ai/evaluator.py(orchestration pipeline)POST /api/ideas/{id}/ai-evaluateendpoint- Tests for AI module (mocked Anthropic client)
phase 3 — demo mode (3-4 commits)
- Demo mode middleware (write protection)
- Rate limit middleware
- Demo seed data (18 curated ideas)
- Startup seeding in lifespan hook
phase 4 — frontend foundation (6-8 commits)
- Scaffold Flutter project, pubspec.yaml, analysis_options
- Core layer (theme, errors, network, logging)
- Ideas feature domain + data layers
- Ideas feature presentation (list screen, filter bar, cards)
- Idea detail screen with basic layout
- GoRouter setup + AppScaffold with navigation
phase 5 — evaluation & charts (5-6 commits)
- Evaluation feature domain + data layers
- Score radar chart widget (fl_chart)
- Metric score card / list widgets
- AI evaluation controller + evaluation screen
- Loading/error/success states
phase 6 — dashboard & comparison (4-5 commits)
- Dashboard feature (stats, top ideas, category chart)
- Comparison feature domain + data
- Comparison screen with overlapping radar charts
- Comparison score table
phase 7 — deployment & polish (3-4 commits)
- Dockerfile and docker-compose.yml
- Flutter web build integration with backend
- Portfolio site project entry
- 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 |