Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| 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: | |
| 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: | |
| 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 | |
| # ============================================================================ | |
| 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 | |
| """ | |