SPG_ML / API_INTEGRATION_GUIDE.py
meetmendapara's picture
Added Personalization Models
5059de5
"""
API Integration Guide - Applying Dynamic Validation to COGNEXA ML Endpoints
This guide shows how to integrate the DynamicRequestValidator from dynamic_api_handler.py
into your FastAPI endpoints to fix 422 validation errors.
PROBLEM: The following endpoints return 422 errors due to strict Pydantic validation:
- /api/personality/analyze-responses
- /api/productivity/forecast
- /api/personalization/predict-engagement
SOLUTION: Replace Pydantic BaseModel with flexible dynamic validation
"""
# ============================================================================
# STEP 1: Import the Dynamic Validator
# ============================================================================
from fastapi import FastAPI, Body
from fastapi.responses import JSONResponse
app = FastAPI()
from ML.dynamic_api_handler import DynamicRequestValidator, FlexibleRequestHandler
# Helper function stubs (API integration guide only)
def analyze_personality_scores(responses: dict, response_scale_max: int):
return {"responses": responses, "scale": response_scale_max}
def generate_forecast(user_id: str, historical_data: list, forecast_days: int, personality: dict = None):
return {"user_id": user_id, "forecast_days": forecast_days, "data_points": len(historical_data)}
def predict_user_engagement(user_id: str, task_id: str, task: dict, user_profile: dict = None):
return 0.5
def run_prediction(user_id: str, complexity: float, count: int):
return {"user_id": user_id, "complexity": complexity, "count": count}
# ============================================================================
# STEP 2: Update Personality Endpoint
# ============================================================================
# BEFORE (Strict Pydantic - will fail on "4.5" instead of 4.5):
"""
from pydantic import BaseModel
class PersonalityResponse(BaseModel):
responses: dict
response_scale_max: int
@app.post("/api/personality/analyze-responses")
async def analyze_responses(request: PersonalityResponse):
# If user sends {"q1": "4"} instead of {"q1": 4}, this fails with 422
...
"""
# AFTER (Flexible Dynamic Validation):
from fastapi import Body
from fastapi.responses import JSONResponse
@app.post("/api/personality/analyze-responses")
async def analyze_responses(request: dict = Body(...)):
"""
Flexible endpoint that coerces types and handles missing fields.
Accepts:
- {"q1": 4, "q2": "3.5", ...} βœ… Coerces string to float
- {"q1": 4} βœ… Fills missing fields with defaults
- {"q1": "abc"} βœ… Returns helpful error message instead of 422
"""
# Validate and coerce the request
try:
validated = DynamicRequestValidator.validate_personality_responses(request)
# Proceed with analysis
result = analyze_personality_scores(
responses=validated['responses'],
response_scale_max=validated['response_scale_max']
)
return JSONResponse(
status_code=200,
content={"status": "success", "analysis": result}
)
except ValueError as e:
return JSONResponse(
status_code=400, # Bad Request (not 422)
content={"error": str(e)}
)
# ============================================================================
# STEP 3: Update Productivity Forecast Endpoint
# ============================================================================
# BEFORE:
"""
class ProductivityForecast(BaseModel):
user_id: str
forecast_days: int
historical_data: list
@app.post("/api/productivity/forecast")
async def forecast_productivity(request: ProductivityForecast):
# Strict validation fails on type mismatches
...
"""
# AFTER:
@app.post("/api/productivity/forecast")
async def forecast_productivity(request: dict = Body(...)):
"""
Flexible forecast endpoint with intelligent type coercion.
Accepts:
- Different data formats
- Missing optional fields (uses defaults)
- String numbers ("7" β†’ 7)
"""
try:
validated = DynamicRequestValidator.validate_productivity_forecast(request)
# Proceed with forecasting
forecast = generate_forecast(
user_id=validated['user_id'],
historical_data=validated['historical_data'],
forecast_days=validated['forecast_days'],
personality=validated.get('personality')
)
return JSONResponse(
status_code=200,
content={"status": "success", "forecast": forecast}
)
except ValueError as e:
return JSONResponse(
status_code=400,
content={"error": str(e)}
)
# ============================================================================
# STEP 4: Update Engagement Prediction Endpoint
# ============================================================================
# BEFORE:
"""
class EngagementPrediction(BaseModel):
user_id: str
task_id: str
task: dict
@app.post("/api/personalization/predict-engagement")
async def predict_engagement(request: EngagementPrediction):
# Fails on any type mismatch
...
"""
# AFTER:
@app.post("/api/personalization/predict-engagement")
async def predict_engagement(request: dict = Body(...)):
"""
Flexible engagement prediction with flexible input handling.
Accepts:
- Various input formats
- Flexible data structures
- Intelligent field coercion
"""
try:
validated = DynamicRequestValidator.validate_engagement_prediction(request)
# Proceed with prediction
engagement_score = predict_user_engagement(
user_id=validated['user_id'],
task_id=validated['task_id'],
task=validated['task'],
user_profile=validated.get('user_profile', {})
)
return JSONResponse(
status_code=200,
content={
"status": "success",
"engagement_prediction": engagement_score
}
)
except ValueError as e:
return JSONResponse(
status_code=400,
content={"error": str(e)}
)
# ============================================================================
# STEP 5: Alternative - Use FlexibleRequestHandler for Complex Validation
# ============================================================================
@app.post("/api/complex/predict")
async def complex_prediction(request: dict = Body(...)):
"""
For more complex validation logic, use FlexibleRequestHandler.
"""
handler = FlexibleRequestHandler()
try:
# Coerce individual fields
user_id = handler.coerce_value(request.get('user_id'), str, required=True)
complexity = handler.coerce_value(request.get('complexity'), float) # "4.5" β†’ 4.5
count = handler.coerce_value(request.get('count'), int, default=0) # "5" β†’ 5
# Proceed with prediction
result = run_prediction(
user_id=user_id,
complexity=complexity,
count=count
)
return {"status": "success", "result": result}
except (ValueError, TypeError) as e:
return JSONResponse(
status_code=400,
content={"error": f"Invalid input: {str(e)}"}
)
# ============================================================================
# STEP 6: Migration Checklist
# ============================================================================
"""
CHECKLIST FOR MIGRATING ENDPOINTS:
1. βœ… Identify problematic endpoints (search for Pydantic BaseModel usage)
- /api/personality/analyze-responses
- /api/productivity/forecast
- /api/personalization/predict-engagement
2. βœ… Replace endpoint signature:
OLD: def endpoint(request: PydanticModel):
NEW: def endpoint(request: dict = Body(...)):
3. βœ… Add DynamicRequestValidator call:
validated = DynamicRequestValidator.validate_*(request)
4. βœ… Update field access:
OLD: request.field_name
NEW: validated['field_name']
5. βœ… Add error handling:
try:
# validation & processing
except ValueError as e:
return JSONResponse(status_code=400, content={"error": str(e)})
6. βœ… Test with various input types:
- {"field": 5} # integer
- {"field": "5"} # string number
- {"field": 5.5} # float
- {"field": "5.5"} # string float
- {} # missing (should use defaults)
- {"field": "invalid"} # invalid (should return 400, not 422)
7. βœ… Verify 422 errors β†’ 400 errors:
- Invalid input now returns 400 Bad Request
- Type coercion handles edge cases
- Helpful error messages provided
8. βœ… Test all endpoints after migration:
python test_api_endpoints.py
"""
# ============================================================================
# STEP 7: Example Requests (cURL)
# ============================================================================
"""
# Test Personality Analysis Endpoint
curl -X POST http://localhost:8000/api/personality/analyze-responses \\
-H "Content-Type: application/json" \\
-d '{
"responses": {
"q1": 4,
"q2": "3.5",
"q3": 5
},
"response_scale_max": 5
}'
# Expected Response:
{
"status": "success",
"analysis": { ... }
}
# Test Productivity Forecast Endpoint
curl -X POST http://localhost:8000/api/productivity/forecast \\
-H "Content-Type: application/json" \\
-d '{
"user_id": "user123",
"forecast_days": "7",
"historical_data": [
{"date": "2026-03-20", "completed_tasks": "5"}
]
}'
# Expected Response:
{
"status": "success",
"forecast": { ... }
}
# Test Engagement Prediction Endpoint
curl -X POST http://localhost:8000/api/personalization/predict-engagement \\
-H "Content-Type: application/json" \\
-d '{
"user_id": "user123",
"task_id": "task456",
"task": {
"title": "Review notes",
"category": "WORK",
"priority": "MEDIUM"
}
}'
# Expected Response:
{
"status": "success",
"engagement_prediction": 0.78
}
"""
# ============================================================================
# STEP 8: Validation Schemas (Reference)
# ============================================================================
"""
PERSONALITY RESPONSE VALIDATION:
- responses: dict (required)
- Keys: string (question IDs)
- Values: int/float (scores, will be coerced)
- response_scale_max: int (default: 5)
- Will coerce string to int
PRODUCTIVITY FORECAST VALIDATION:
- user_id: str (required)
- historical_data: list (required)
- List of dicts with productivity data
- forecast_days: int (default: 7)
- Will coerce string to int
- personality: dict (optional)
- Big Five personality scores
ENGAGEMENT PREDICTION VALIDATION:
- user_id: str (required)
- task_id: str (required)
- task: dict (required)
- Must contain task details
- user_profile: dict (optional)
- Optional user profile data
"""
# ============================================================================
# RESOURCES
# ============================================================================
"""
Files to reference:
- /ML/dynamic_api_handler.py - Full validation implementation
- /ML/test_api_endpoints.py - Comprehensive API tests
- /ML/api_patches.md - This file
- /ML/test_ml_service.py - Unit tests for validators
Commands:
- Test validators: python test_ml_service.py
- Test endpoints: python test_api_endpoints.py
- Run server: python main.py
Common Issues & Solutions:
1. "422 Unprocessable Entity"
β†’ Use DynamicRequestValidator instead of strict Pydantic
2. "400 Bad Request" with helpful message
β†’ This is expected for invalid data (better than 422)
3. String numbers not coercing
β†’ Ensure coerce_value() is being called
β†’ Check type hints in validation method
4. Missing fields causing errors
β†’ Set defaults in validation method
β†’ Use .get() with defaults in validated dict
"""