Skip to main content

Introduction

The Interview Simulator follows a clean, layered architecture pattern that separates concerns across four distinct layers: Routes, Services, Repositories, and Models. This architecture promotes maintainability, testability, and scalability.

Architectural Diagram

┌─────────────────────────────────────────┐
│         Flask Application               │
│  ┌──────────────────────────────────┐  │
│  │     Routes (app/routes)          │  │
│  └──────────┬───────────────────────┘  │
│             ↓                            │
│  ┌──────────────────────────────────┐  │
│  │   Services Layer (app/services)  │  │
│  │  • SessionService                │  │
│  │  • DocumentService               │  │
│  │  • InterviewService              │  │
│  │  • FeedbackService               │  │
│  └──────────┬───────────────────────┘  │
│             ↓                            │
│  ┌──────────────────────────────────┐  │
│  │   Repositories (app/repositories)│  │
│  │  • SessionRepository             │  │
│  │  • MessageRepository             │  │
│  │  • FeedbackRepository            │  │
│  │  • FileRepository                │  │
│  └──────────┬───────────────────────┘  │
│             ↓                            │
│  ┌──────────────────────────────────┐  │
│  │   Data Layer (app/models)        │  │
│  │  • SQLite Database               │  │
│  │  • SQLAlchemy ORM                │  │
│  └──────────────────────────────────┘  │
└─────────────────────────────────────────┘
         │                    │
         ↓                    ↓
   ┌──────────┐        ┌──────────┐
   │ AI Client│        │  HTMX    │
   │(client/) │        │ Frontend │
   └──────────┘        └──────────┘

Layer Responsibilities

The Routes layer handles HTTP requests and responses. It’s responsible for:
  • Receiving and validating incoming HTTP requests
  • Extracting data from request forms, JSON, or query parameters
  • Calling appropriate service methods
  • Handling errors and returning appropriate HTTP responses
  • Managing Flask sessions for user state
Example from session_routes.py:712:
@bp.route("/session/create", methods=["POST"])
def create_session():
    try:
        job_title = request.form.get("job_title", "")
        company_name = request.form.get("company_name", "")
        
        session_service = _get_session_service()
        new_session = session_service.create_session(job_title, company_name)
        
        return redirect(url_for("document.upload_page", session_id=new_session.id))
    except ValidationError as e:
        flash(str(e), "error")
        return redirect(url_for("session.index"))
Key files:
  • session_routes.py - Session CRUD operations
  • document_routes.py - CV and job description uploads
  • interview_routes.py - Interview conversation flow
  • feedback_routes.py - Feedback generation and display
  • errors.py - Error handlers
The Services layer contains business logic and orchestration. It:
  • Implements core business rules and validation
  • Orchestrates operations across multiple repositories
  • Coordinates with external systems (AI providers)
  • Handles complex workflows that span multiple data entities
Example from interview_service.py:19:
class InterviewService:
    MAX_QUESTIONS = 8

    def __init__(self, session_repository, message_repository, ai_client):
        self.session_repo = session_repository
        self.message_repo = message_repository
        self.ai_client = ai_client

    def submit_answer(self, session_id: int, answer: str) -> dict:
        if not answer or not answer.strip():
            raise ValidationError("Answer cannot be empty.")
        
        self.message_repo.create_message(session_id, "user", answer)
        question_count = self.message_repo.count_messages(session_id, role="assistant")
        
        if question_count >= self.MAX_QUESTIONS:
            return {"is_complete": True}
        
        # Generate next question using AI
        next_question = self.ai_client.generate_followup_question(...)
        return {"next_question": next_question, "is_complete": False}
Key services:
  • SessionService - Session lifecycle management
  • DocumentService - File upload and text extraction
  • InterviewService - Interview flow orchestration
  • FeedbackService - Feedback generation and analysis
The Repositories layer abstracts database operations. It:
  • Provides a clean interface for data access
  • Encapsulates all SQLAlchemy queries
  • Handles database transactions and error handling
  • Manages eager/lazy loading strategies
Example from session_repository.py:8:
class SessionRepository:
    def create(self, job_title: str, company_name: str, user_id: int | None = None):
        session = Session(job_title=job_title, company_name=company_name, user_id=user_id)
        db.session.add(session)
        db.session.commit()
        db.session.refresh(session)
        return session
    
    def get_session_with_messages(self, session_id: int):
        return Session.query.options(db.joinedload(Session.messages)).get(session_id)
Key repositories:
  • SessionRepository - Session CRUD and queries
  • MessageRepository - Conversation message storage
  • FeedbackRepository - Feedback persistence
  • FileRepository - Uploaded file management
The Models layer defines data structures and relationships. It:
  • Defines SQLAlchemy ORM models
  • Declares database schema through Python classes
  • Establishes relationships between entities
  • Provides data validation at the database level
See the Database Schema page for detailed model definitions.

Tech Stack

Backend Framework

  • Flask 3.0: Lightweight Python web framework
  • SQLAlchemy: SQL toolkit and ORM
  • SQLite: Development database (PostgreSQL recommended for production)

AI Integration

  • Google Gemini: Primary AI provider for question generation
  • OpenRouter: Alternative AI provider with multiple model options
  • Provider Pattern: Abstracted AIProvider protocol for easy provider switching
  • Tenacity: Retry logic with exponential backoff for API reliability

Frontend

  • HTMX: Dynamic server interactions without complex JavaScript
  • Jinja2: Server-side HTML templating
  • Custom CSS: Styling with main.css

Document Processing

  • pdfplumber: PDF text extraction
  • python-docx: Word document parsing
  • Plain text: Direct text input support

Key Design Decisions

1. Layered Architecture

Why this approach?
  • Separation of Concerns: Each layer has a single, well-defined responsibility
  • Testability: Services can be tested in isolation by mocking repositories
  • Maintainability: Changes to one layer don’t cascade to others
  • Scalability: Layers can be optimized or replaced independently
Trade-offs:
  • More boilerplate code for simple CRUD operations
  • Requires discipline to maintain layer boundaries

2. HTMX Over React/Vue

Why HTMX?
  • Server-side rendering: All logic stays in Python
  • Minimal JavaScript: No build process, webpack, or npm dependencies
  • Progressive enhancement: Works without JavaScript enabled
  • Fast development: No context switching between frontend/backend
Trade-offs:
  • Limited client-side interactivity
  • Not ideal for complex SPAs
  • Less suitable for offline-first applications

3. Provider Pattern for AI

Implementation:
class AIProvider(Protocol):
    def generate_response(self, prompt: str) -> str:
        ...

class GeminiProvider:
    def generate_response(self, prompt: str) -> str:
        # Gemini-specific implementation

class OpenRouterProvider:
    def generate_response(self, prompt: str) -> str:
        # OpenRouter-specific implementation
Benefits:
  • Easy to switch between AI providers
  • Can add new providers without changing business logic
  • Supports fallback strategies (try Gemini, fall back to OpenRouter)
  • Enables A/B testing of different AI models

4. Session-Based State Management

Why sessions instead of authentication?
  • MVP scope: Authentication adds complexity for initial launch
  • Quick onboarding: Users can start interviews immediately
  • Privacy: No email or personal data collection required
  • Simplicity: Flask sessions are built-in and straightforward
Future consideration: Add optional user accounts for:
  • Cross-device session access
  • Long-term progress tracking
  • Premium features

5. Document Parser Abstraction

Why abstract file parsing?
  • Single interface for PDF, DOCX, and TXT files
  • Graceful error handling for corrupted files
  • Easy to add new formats (e.g., Google Docs export)
  • Consistent text extraction across formats

Application Flow

  1. Create Session → User provides job title and company name
  2. Upload Documents → CV and job description uploaded and parsed
  3. Start Interview → AI generates first question based on documents
  4. Conversation Loop → User answers, AI asks follow-up (up to 8 questions)
  5. Generate Feedback → AI analyzes performance and provides scores
  6. View Results → User sees strengths, weaknesses, and CV improvement tips

Next Steps

Build docs developers (and LLMs) love