MuhammadSaad16's picture
Upload 112 files
971b4ea verified

Data Model: Content Personalization API

Feature Branch: 005-content-personalize Created: 2025-12-14 Status: Complete

Overview

This feature uses the existing User model and introduces request/response schemas only. No new database tables are required.


Existing Entity: User

The User entity already contains all fields needed for personalization.

Location: app/models/user.py

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   User                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ id: Integer (PK)                            β”‚
β”‚ username: String (nullable, unique)         β”‚
β”‚ email: String(255) (unique, not null)       β”‚
β”‚ hashed_password: String(60) (not null)      β”‚
β”‚ software_level: String(20) (default: "beginner")     β”‚
β”‚ hardware_level: String(20) (default: "none")         β”‚
β”‚ learning_goals: Text (default: "")          β”‚
β”‚ created_at: DateTime (auto)                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Relevant Fields for Personalization

Field Type Values Usage
software_level String(20) "beginner", "intermediate", "advanced" Determines code/software content complexity
hardware_level String(20) "none", "basic", "experienced" Determines hardware concept explanation depth
learning_goals Text Free-form Used to emphasize relevant topics

Enums (Reference)

class SoftwareLevel(str, Enum):
    beginner = "beginner"
    intermediate = "intermediate"
    advanced = "advanced"

class HardwareLevel(str, Enum):
    none = "none"
    basic = "basic"
    experienced = "experienced"

New Schema: PersonalizeRequest

Purpose: Validate incoming personalization requests

Location: app/schemas/personalize.py

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            PersonalizeRequest               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ content: str (required, non-empty)          β”‚
β”‚ user_id: int (required, positive)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Validation Rules

Field Rule Error
content Non-empty after strip "Content cannot be empty"
content Max 50,000 characters "Content exceeds maximum length of 50000 characters"
user_id Positive integer "User ID must be a positive integer"

New Schema: PersonalizeResponse

Purpose: Structure the API response

Location: app/schemas/personalize.py

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            PersonalizeResponse              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ personalized_content: str                   β”‚
β”‚ adjustments_made: str                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Field Descriptions

Field Description Example
personalized_content The content adapted for the user's level Full adapted text
adjustments_made Human-readable description of changes "Simplified technical terminology, added explanations for variables and loops, included beginner-friendly examples"

Entity Relationships

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      User       β”‚ ◄─────── β”‚   PersonalizeRequest    β”‚
β”‚                 β”‚  user_id β”‚                         β”‚
β”‚ id (PK)         β”‚          β”‚ content                 β”‚
β”‚ software_level  β”‚          β”‚ user_id                 β”‚
β”‚ hardware_level  β”‚          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ learning_goals  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β”‚ Profile data used for
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚             OpenAI API Call                  β”‚
β”‚                                              β”‚
β”‚  System: Personalization rules + user profileβ”‚
β”‚  User: Original content                      β”‚
β”‚                                              β”‚
β”‚  Returns: Adapted content + adjustments      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   PersonalizeResponse   β”‚
β”‚                         β”‚
β”‚ personalized_content    β”‚
β”‚ adjustments_made        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data Flow

  1. Request arrives with content and user_id
  2. Validation ensures content is non-empty and within limits
  3. User lookup retrieves profile from database by user_id
  4. Personalization sends content + profile to OpenAI
  5. Response returns adapted content and adjustment description

No Database Migrations Required

This feature:

  • Uses existing User table (no schema changes)
  • Does not persist personalized content (per spec - out of scope)
  • Only requires Pydantic schemas for request/response validation

Schema Implementation

# app/schemas/personalize.py

from pydantic import BaseModel, field_validator


class PersonalizeRequest(BaseModel):
    content: str
    user_id: int

    @field_validator('content')
    @classmethod
    def content_not_empty(cls, v):
        if not v or not v.strip():
            raise ValueError('Content cannot be empty')
        v = v.strip()
        if len(v) > 50000:
            raise ValueError('Content exceeds maximum length of 50000 characters')
        return v

    @field_validator('user_id')
    @classmethod
    def user_id_positive(cls, v):
        if v <= 0:
            raise ValueError('User ID must be a positive integer')
        return v


class PersonalizeResponse(BaseModel):
    personalized_content: str
    adjustments_made: str