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)
```python
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
```python
# 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
```