feat: add proper Pydantic response_model to all stats endpoints

- Create comprehensive stats schemas in models/schema/stats.py:
  - ImportStatsResponse, UserStatsResponse, ProductStatsResponse
  - PlatformStatsResponse, AdminDashboardResponse
  - VendorDashboardStatsResponse with nested models
  - VendorAnalyticsResponse, CodeQualityDashboardStatsResponse
- Move DashboardStatsResponse from code_quality.py to schema file
- Fix get_vendor_statistics() to return pending_vendors field
- Fix get_vendor_stats() to return flat structure matching schema
- Add response_model to all stats endpoints:
  - GET /admin/dashboard -> AdminDashboardResponse
  - GET /admin/dashboard/stats/platform -> PlatformStatsResponse
  - GET /admin/marketplace-import-jobs/stats -> ImportStatsResponse
  - GET /vendor/dashboard/stats -> VendorDashboardStatsResponse
  - GET /vendor/analytics -> VendorAnalyticsResponse
- Enhance API-001 architecture rule with detailed guidance
- Add SVC-007 rule for service/schema compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-11 17:29:35 +01:00
parent f2af3aae29
commit 22c4937779
8 changed files with 501 additions and 119 deletions

View File

@@ -41,16 +41,51 @@ api_endpoint_rules:
description: |
All API endpoints must use Pydantic models (BaseModel) for request bodies
and response models. Never use raw dicts or SQLAlchemy models directly.
WHY THIS MATTERS:
- Type safety: Pydantic validates response structure at runtime
- Documentation: OpenAPI/Swagger auto-generates accurate docs
- Contract stability: Schema changes are explicit and reviewable
- IDE support: Consumers get autocomplete and type hints
- Prevents bugs: Field name mismatches caught immediately
COMMON VIOLATION: Service returns dict, frontend expects different field names.
Example: Service returns {"total_imports": 5} but frontend expects {"total": 5}.
With response_model, this mismatch is caught immediately.
SCHEMA LOCATION: All response schemas must be defined in models/schema/*.py,
never inline in endpoint files. This ensures schemas are reusable and discoverable.
pattern:
file_pattern: "app/api/v1/**/*.py"
anti_patterns:
- "return dict"
- "-> dict"
- "return db_object"
- "return {" # Returning inline dict literal
example_good: |
@router.post("/vendors", response_model=VendorResponse)
async def create_vendor(vendor: VendorCreate):
return vendor_service.create_vendor(db, vendor)
# In models/schema/stats.py
class ImportStatsResponse(BaseModel):
total: int
pending: int
completed: int
failed: int
# In app/api/v1/admin/marketplace.py
@router.get("/stats", response_model=ImportStatsResponse)
def get_import_statistics(db: Session = Depends(get_db)):
return stats_service.get_import_statistics(db)
example_bad: |
# ❌ WRONG: No response_model, returns raw dict
@router.get("/stats")
def get_import_statistics(db: Session = Depends(get_db)):
return stats_service.get_import_statistics(db) # Returns dict
# ❌ WRONG: Schema defined inline in endpoint file
class MyResponse(BaseModel): # Should be in models/schema/
...
@router.get("/data", response_model=MyResponse)
def get_data(): ...
- id: "API-002"
name: "Endpoint must NOT contain business logic"
@@ -238,6 +273,42 @@ service_layer_rules:
exceptions:
- "log_service.py" # Audit logs may need immediate commits
- id: "SVC-007"
name: "Service return types must match API response schemas"
severity: "error"
description: |
When a service method's return value will be used as an API response,
the returned dict keys MUST match the corresponding Pydantic schema fields.
This prevents the common bug where:
- Service returns {"total_imports": 5, "completed_imports": 3}
- Schema expects {"total": 5, "completed": 3}
- Frontend receives wrong/empty values
RECOMMENDED PATTERNS:
1. Return Pydantic model directly from service:
def get_stats(self, db: Session) -> StatsResponse:
return StatsResponse(total=count, completed=done)
2. Return dict with schema-matching keys:
def get_stats(self, db: Session) -> dict:
return {"total": count, "completed": done} # Matches StatsResponse
3. Document the expected schema in service docstring:
def get_stats(self, db: Session) -> dict:
\"\"\"
Returns dict compatible with StatsResponse schema.
Keys: total, pending, completed, failed
\"\"\"
TESTING: Write tests that validate service output against schema:
result = service.get_stats(db)
StatsResponse(**result) # Raises if keys don't match
pattern:
file_pattern: "app/services/**/*.py"
check: "schema_compatibility"
# ============================================================================
# MODEL RULES (models/database/*.py, models/schema/*.py)
# ============================================================================