Created complete test coverage for risk assessment endpoints:
- 25 test cases covering all 3 endpoints
- POST /risk-assessments (11 tests)
- GET /risk-assessments (5 tests)
- GET /risk-assessments/{id} (5 tests)
- Edge cases and error handling (4 tests)
Test features:
✅ Access control (guardian, psychologist, administrator)
✅ Ollama integration (mocked with AsyncMock)
✅ Fallback mechanisms
✅ Pagination and filtering
✅ Error handling and edge cases
✅ Data validation
Dependencies and fixes:
- Updated httpx (0.26.0 → 0.27.2) for pytest-httpx compatibility
- Fixed JWT imports (import jwt → from jose import jwt)
- Fixed Pydantic v2 field_validator syntax
- Fixed model import conflicts (removed duplicate User model)
- Updated backend/models/__init__.py to import left_behind_children models
- Added missing auth exports (blacklist_token, is_token_blacklisted, can_access_risk_assessment)
Known issue (⚠️ BLOCKING):
- SQLite UUID type incompatibility preventing tests from running
- Error: SQLiteTypeCompiler can't render UUID type
- Need to implement UUID-to-String adapter for SQLite in tests
- All 25 tests fail at setup phase
Files created/modified:
- tests/unit/test_api_risk_assessments.py (NEW - 700+ lines)
- requirements.txt (updated httpx, commented safety)
- backend/auth/__init__.py (added exports)
- backend/auth/utils.py (fixed JWT import)
- backend/auth/dependencies.py (fixed JWT import)
- backend/api/data.py (fixed Pydantic validator)
- backend/models/__init__.py (switched to left_behind_children models)
Next steps:
1. Fix UUID/SQLite compatibility (use String(36) or UUID adapter)
2. Run tests successfully
3. Achieve >95% coverage target
Progress: Issue #37 - 70% complete (tests written, need execution)
Related: #38, #39, #40, #41, #42
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1500 lines
40 KiB
YAML
1500 lines
40 KiB
YAML
openapi: 3.0.3
|
|
|
|
info:
|
|
title: Left-behind Children Psychological Risk Detection Platform API
|
|
version: 1.0.0
|
|
description: |
|
|
API for early detection and prevention of psychological risks in left-behind children.
|
|
|
|
**Key Features:**
|
|
- Multi-modal data collection (voice, text, behavioral)
|
|
- AI-powered risk assessment (90%+ accuracy)
|
|
- Resource library (20,000+ materials)
|
|
- Communication & collaboration tools
|
|
- Role-based access control (6 roles)
|
|
|
|
**Security:** All endpoints require authentication unless marked as public.
|
|
All PII is encrypted (AES-256 at rest, TLS 1.3 in transit).
|
|
|
|
**Rate Limiting:** 1000 requests/hour per user, 10000 requests/hour per organization.
|
|
|
|
**Support:** Technical support available 24/7 via crisis hotline integration.
|
|
|
|
contact:
|
|
name: Platform API Support
|
|
email: [email protected]
|
|
url: https://left-behind-children.org/api-docs
|
|
|
|
license:
|
|
name: Proprietary
|
|
url: https://left-behind-children.org/license
|
|
|
|
servers:
|
|
- url: https://api.left-behind-children.org/v1
|
|
description: Production server
|
|
- url: https://api-staging.left-behind-children.org/v1
|
|
description: Staging server
|
|
- url: http://localhost:8000/v1
|
|
description: Local development server
|
|
|
|
tags:
|
|
- name: Authentication
|
|
description: User authentication and authorization
|
|
- name: Users
|
|
description: User management (children, guardians, psychologists, etc.)
|
|
- name: Risk Assessment
|
|
description: Psychological risk scoring and early warnings
|
|
- name: Data Collection
|
|
description: Multi-modal data ingestion (voice, text, behavioral)
|
|
- name: Resources
|
|
description: Intervention resource library
|
|
- name: Communication
|
|
description: Chat, video calls, consultations
|
|
- name: Analytics
|
|
description: Dashboards and reports
|
|
- name: Health
|
|
description: System health and monitoring
|
|
|
|
security:
|
|
- BearerAuth: []
|
|
|
|
paths:
|
|
# ============================================
|
|
# HEALTH & MONITORING
|
|
# ============================================
|
|
|
|
/health:
|
|
get:
|
|
tags:
|
|
- Health
|
|
summary: Health check endpoint
|
|
description: Returns system health status (CPU, memory, database, dependencies)
|
|
operationId: getHealth
|
|
security: [] # Public endpoint
|
|
responses:
|
|
'200':
|
|
description: System is healthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/HealthResponse'
|
|
example:
|
|
status: healthy
|
|
timestamp: "2025-12-31T12:00:00Z"
|
|
checks:
|
|
database: healthy
|
|
redis: healthy
|
|
speech_api: healthy
|
|
nlp_api: healthy
|
|
'503':
|
|
description: System is unhealthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/HealthResponse'
|
|
|
|
/health/ready:
|
|
get:
|
|
tags:
|
|
- Health
|
|
summary: Readiness check
|
|
description: Check if system is ready to accept traffic
|
|
operationId: getReadiness
|
|
security: []
|
|
responses:
|
|
'200':
|
|
description: System is ready
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReadinessResponse'
|
|
'503':
|
|
description: System is not ready
|
|
|
|
/metrics:
|
|
get:
|
|
tags:
|
|
- Health
|
|
summary: Prometheus metrics
|
|
description: Returns Prometheus-compatible metrics
|
|
operationId: getMetrics
|
|
security: []
|
|
responses:
|
|
'200':
|
|
description: Metrics in Prometheus format
|
|
content:
|
|
text/plain:
|
|
example: |
|
|
# HELP http_requests_total Total HTTP requests
|
|
# TYPE http_requests_total counter
|
|
http_requests_total{method="GET",endpoint="/health",status="200"} 1234
|
|
|
|
# ============================================
|
|
# AUTHENTICATION
|
|
# ============================================
|
|
|
|
/auth/register:
|
|
post:
|
|
tags:
|
|
- Authentication
|
|
summary: Register new user
|
|
description: |
|
|
Register a new user account.
|
|
- Children under 14 require parental consent (consent_token)
|
|
- Phone number verification via SMS OTP
|
|
operationId: registerUser
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserRegistration'
|
|
example:
|
|
phone: "+8613800138000"
|
|
password: "SecurePassword123!"
|
|
role: child
|
|
name: "小明"
|
|
age: 12
|
|
consent_token: "parent-consent-abc123"
|
|
responses:
|
|
'201':
|
|
description: User registered successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'409':
|
|
description: Phone number already registered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/auth/login:
|
|
post:
|
|
tags:
|
|
- Authentication
|
|
summary: User login
|
|
description: Authenticate user and return JWT token (7-day validity)
|
|
operationId: loginUser
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginRequest'
|
|
example:
|
|
phone: "+8613800138000"
|
|
password: "SecurePassword123!"
|
|
responses:
|
|
'200':
|
|
description: Login successful
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginResponse'
|
|
example:
|
|
access_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
token_type: "bearer"
|
|
expires_in: 604800
|
|
user:
|
|
id: "user-123"
|
|
phone: "+8613800138000"
|
|
name: "小明"
|
|
role: child
|
|
age: 12
|
|
created_at: "2025-01-01T00:00:00Z"
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
/auth/logout:
|
|
post:
|
|
tags:
|
|
- Authentication
|
|
summary: User logout
|
|
description: Invalidate JWT token
|
|
operationId: logoutUser
|
|
responses:
|
|
'204':
|
|
description: Logout successful
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
|
/auth/refresh:
|
|
post:
|
|
tags:
|
|
- Authentication
|
|
summary: Refresh access token
|
|
description: Get a new access token using refresh token
|
|
operationId: refreshToken
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
refresh_token:
|
|
type: string
|
|
required:
|
|
- refresh_token
|
|
responses:
|
|
'200':
|
|
description: Token refreshed
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
|
# ============================================
|
|
# USER MANAGEMENT
|
|
# ============================================
|
|
|
|
/users/me:
|
|
get:
|
|
tags:
|
|
- Users
|
|
summary: Get current user profile
|
|
description: Retrieve authenticated user's profile
|
|
operationId: getCurrentUser
|
|
responses:
|
|
'200':
|
|
description: User profile retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
|
patch:
|
|
tags:
|
|
- Users
|
|
summary: Update current user profile
|
|
description: Update authenticated user's profile (partial update)
|
|
operationId: updateCurrentUser
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserUpdateRequest'
|
|
responses:
|
|
'200':
|
|
description: User profile updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
|
|
/users/{user_id}:
|
|
get:
|
|
tags:
|
|
- Users
|
|
summary: Get user by ID
|
|
description: |
|
|
Retrieve user profile by ID.
|
|
Access control:
|
|
- Child: Can only view own profile
|
|
- Guardian/Parent: Can view assigned children
|
|
- Psychologist: Can view assigned cases
|
|
- Administrator: Can view all users
|
|
operationId: getUserById
|
|
parameters:
|
|
- name: user_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: User ID (UUID)
|
|
responses:
|
|
'200':
|
|
description: User profile retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
# ============================================
|
|
# RISK ASSESSMENT
|
|
# ============================================
|
|
|
|
/risk-assessments:
|
|
post:
|
|
tags:
|
|
- Risk Assessment
|
|
summary: Create risk assessment
|
|
description: |
|
|
Trigger risk assessment for a user based on collected data.
|
|
- Real-time assessment: <500ms latency
|
|
- Returns risk score (0-100) and category (Low/Medium/High/Critical)
|
|
- Critical alerts (76-100) trigger automatic notifications
|
|
operationId: createRiskAssessment
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RiskAssessmentRequest'
|
|
example:
|
|
user_id: "child-123"
|
|
assessment_type: comprehensive
|
|
data_sources:
|
|
- voice_analysis_id: "voice-456"
|
|
- text_analysis_id: "text-789"
|
|
- behavioral_data_id: "behavior-012"
|
|
responses:
|
|
'201':
|
|
description: Risk assessment created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RiskAssessmentResponse'
|
|
example:
|
|
id: "assessment-abc123"
|
|
user_id: "child-123"
|
|
risk_score: 45
|
|
risk_category: medium
|
|
risk_factors:
|
|
- category: anxiety
|
|
severity: moderate
|
|
confidence: 0.87
|
|
- category: academic_stress
|
|
severity: high
|
|
confidence: 0.92
|
|
recommendations:
|
|
- resource_id: "resource-001"
|
|
title: "应对焦虑的呼吸练习"
|
|
type: video
|
|
alerts_triggered: []
|
|
created_at: "2025-12-31T12:00:00Z"
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'500':
|
|
$ref: '#/components/responses/InternalServerError'
|
|
|
|
get:
|
|
tags:
|
|
- Risk Assessment
|
|
summary: List risk assessments
|
|
description: |
|
|
Retrieve risk assessments with pagination and filtering.
|
|
Access control applies (users can only see their own or assigned cases).
|
|
operationId: listRiskAssessments
|
|
parameters:
|
|
- $ref: '#/components/parameters/PageParam'
|
|
- $ref: '#/components/parameters/PageSizeParam'
|
|
- name: user_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by user ID
|
|
- name: risk_category
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [low, medium, high, critical]
|
|
description: Filter by risk category
|
|
- name: start_date
|
|
in: query
|
|
schema:
|
|
type: string
|
|
format: date
|
|
description: Filter assessments after this date
|
|
responses:
|
|
'200':
|
|
description: List of risk assessments
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RiskAssessmentList'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
/risk-assessments/{assessment_id}:
|
|
get:
|
|
tags:
|
|
- Risk Assessment
|
|
summary: Get risk assessment by ID
|
|
description: Retrieve detailed risk assessment by ID
|
|
operationId: getRiskAssessmentById
|
|
parameters:
|
|
- name: assessment_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Risk assessment retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RiskAssessmentResponse'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
# ============================================
|
|
# DATA COLLECTION
|
|
# ============================================
|
|
|
|
/data/voice:
|
|
post:
|
|
tags:
|
|
- Data Collection
|
|
summary: Upload voice data
|
|
description: |
|
|
Upload voice recording for analysis.
|
|
- Supports: MP3, WAV, M4A (max 10MB)
|
|
- Processes: Emotion detection, speech rate, dialect recognition
|
|
- Returns: voice_analysis_id for use in risk assessment
|
|
operationId: uploadVoiceData
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
multipart/form-data:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
user_id:
|
|
type: string
|
|
description: User ID
|
|
audio_file:
|
|
type: string
|
|
format: binary
|
|
description: Audio file (MP3, WAV, M4A)
|
|
metadata:
|
|
type: object
|
|
properties:
|
|
duration_seconds:
|
|
type: integer
|
|
context:
|
|
type: string
|
|
description: Context (e.g., "phone_call", "voice_message")
|
|
required:
|
|
- user_id
|
|
- audio_file
|
|
responses:
|
|
'201':
|
|
description: Voice data uploaded
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/VoiceAnalysisResponse'
|
|
example:
|
|
id: "voice-abc123"
|
|
user_id: "child-123"
|
|
status: processing
|
|
estimated_completion: "2025-12-31T12:01:00Z"
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'413':
|
|
description: File too large (max 10MB)
|
|
|
|
/data/text:
|
|
post:
|
|
tags:
|
|
- Data Collection
|
|
summary: Submit text data
|
|
description: |
|
|
Submit text for sentiment and emotion analysis.
|
|
- Supports: Simplified Chinese, Traditional Chinese
|
|
- Processes: Sentiment, emotion, risk keywords
|
|
- Real-time processing: <200ms
|
|
operationId: submitTextData
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TextDataRequest'
|
|
example:
|
|
user_id: "child-123"
|
|
text: "今天心情不太好,感觉压力很大..."
|
|
context: diary_entry
|
|
timestamp: "2025-12-31T12:00:00Z"
|
|
responses:
|
|
'201':
|
|
description: Text analyzed
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TextAnalysisResponse'
|
|
example:
|
|
id: "text-abc123"
|
|
user_id: "child-123"
|
|
sentiment: negative
|
|
sentiment_score: -0.65
|
|
emotions:
|
|
- emotion: sadness
|
|
confidence: 0.78
|
|
- emotion: stress
|
|
confidence: 0.85
|
|
risk_keywords:
|
|
- keyword: "压力"
|
|
category: academic_stress
|
|
created_at: "2025-12-31T12:00:00Z"
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
# ============================================
|
|
# RESOURCES
|
|
# ============================================
|
|
|
|
/resources:
|
|
get:
|
|
tags:
|
|
- Resources
|
|
summary: List intervention resources
|
|
description: |
|
|
Browse resource library with filtering and pagination.
|
|
- 20,000+ resources (videos, audio, articles, tools)
|
|
- Personalized recommendations based on risk profile
|
|
operationId: listResources
|
|
parameters:
|
|
- $ref: '#/components/parameters/PageParam'
|
|
- $ref: '#/components/parameters/PageSizeParam'
|
|
- name: category
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [depression, anxiety, self_harm, social_withdrawal, academic_stress, family_conflict]
|
|
description: Filter by risk category
|
|
- name: resource_type
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [video, audio, article, interactive_tool]
|
|
description: Filter by resource type
|
|
- name: age_group
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [elementary, middle_school, high_school]
|
|
description: Filter by age appropriateness
|
|
- name: recommended
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
description: Only show personalized recommendations
|
|
responses:
|
|
'200':
|
|
description: List of resources
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResourceList'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
/resources/{resource_id}:
|
|
get:
|
|
tags:
|
|
- Resources
|
|
summary: Get resource by ID
|
|
description: Retrieve detailed resource information
|
|
operationId: getResourceById
|
|
parameters:
|
|
- name: resource_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Resource retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ResourceResponse'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
/resources/{resource_id}/track-usage:
|
|
post:
|
|
tags:
|
|
- Resources
|
|
summary: Track resource usage
|
|
description: Record that user accessed a resource (for analytics and recommendations)
|
|
operationId: trackResourceUsage
|
|
parameters:
|
|
- name: resource_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
action:
|
|
type: string
|
|
enum: [viewed, completed, liked, shared]
|
|
duration_seconds:
|
|
type: integer
|
|
description: Time spent (for videos)
|
|
responses:
|
|
'204':
|
|
description: Usage tracked
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
|
|
# ============================================
|
|
# COMMUNICATION
|
|
# ============================================
|
|
|
|
/conversations:
|
|
post:
|
|
tags:
|
|
- Communication
|
|
summary: Start a conversation
|
|
description: |
|
|
Create a new conversation (1-on-1 or group).
|
|
Participants can be: child, parent, guardian, psychologist.
|
|
operationId: createConversation
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ConversationCreateRequest'
|
|
responses:
|
|
'201':
|
|
description: Conversation created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ConversationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
|
|
get:
|
|
tags:
|
|
- Communication
|
|
summary: List conversations
|
|
description: Get list of conversations for current user
|
|
operationId: listConversations
|
|
parameters:
|
|
- $ref: '#/components/parameters/PageParam'
|
|
- $ref: '#/components/parameters/PageSizeParam'
|
|
responses:
|
|
'200':
|
|
description: List of conversations
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ConversationList'
|
|
|
|
/conversations/{conversation_id}/messages:
|
|
post:
|
|
tags:
|
|
- Communication
|
|
summary: Send message
|
|
description: Send a text message in a conversation
|
|
operationId: sendMessage
|
|
parameters:
|
|
- name: conversation_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MessageRequest'
|
|
responses:
|
|
'201':
|
|
description: Message sent
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
|
get:
|
|
tags:
|
|
- Communication
|
|
summary: Get messages
|
|
description: Retrieve messages in a conversation with pagination
|
|
operationId: getMessages
|
|
parameters:
|
|
- name: conversation_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- $ref: '#/components/parameters/PageParam'
|
|
- $ref: '#/components/parameters/PageSizeParam'
|
|
responses:
|
|
'200':
|
|
description: Messages retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MessageList'
|
|
|
|
/consultations:
|
|
post:
|
|
tags:
|
|
- Communication
|
|
summary: Book consultation
|
|
description: Schedule a counseling session with a psychologist
|
|
operationId: bookConsultation
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ConsultationRequest'
|
|
responses:
|
|
'201':
|
|
description: Consultation booked
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ConsultationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'409':
|
|
description: Time slot not available
|
|
|
|
# ============================================
|
|
# ANALYTICS
|
|
# ============================================
|
|
|
|
/analytics/dashboard:
|
|
get:
|
|
tags:
|
|
- Analytics
|
|
summary: Get dashboard data
|
|
description: |
|
|
Retrieve dashboard data based on user role.
|
|
- Child: Personal mood trends
|
|
- Guardian/Parent: Child's risk trends
|
|
- Teacher: Class-level statistics
|
|
- Psychologist: Case load summary
|
|
- Administrator: Platform-wide KPIs
|
|
operationId: getDashboard
|
|
parameters:
|
|
- name: time_range
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [7days, 30days, 90days, 1year]
|
|
description: Time range for analytics
|
|
responses:
|
|
'200':
|
|
description: Dashboard data
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/DashboardResponse'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
|
|
# ============================================
|
|
# COMPONENTS
|
|
# ============================================
|
|
|
|
components:
|
|
securitySchemes:
|
|
BearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: JWT token obtained from /auth/login endpoint
|
|
|
|
parameters:
|
|
PageParam:
|
|
name: page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
default: 1
|
|
description: Page number
|
|
|
|
PageSizeParam:
|
|
name: page_size
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 20
|
|
description: Items per page
|
|
|
|
schemas:
|
|
# Common Schemas
|
|
ErrorResponse:
|
|
type: object
|
|
required:
|
|
- error
|
|
- message
|
|
- trace_id
|
|
properties:
|
|
error:
|
|
type: string
|
|
description: Error code
|
|
message:
|
|
type: string
|
|
description: Human-readable error message
|
|
trace_id:
|
|
type: string
|
|
description: OpenTelemetry trace ID for debugging
|
|
details:
|
|
type: object
|
|
description: Additional error details
|
|
example:
|
|
error: "VALIDATION_ERROR"
|
|
message: "Phone number format is invalid"
|
|
trace_id: "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
|
|
details:
|
|
field: "phone"
|
|
constraint: "E.164 format required"
|
|
|
|
HealthResponse:
|
|
type: object
|
|
required:
|
|
- status
|
|
- timestamp
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [healthy, degraded, unhealthy]
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
trace_id:
|
|
type: string
|
|
description: OpenTelemetry trace ID for debugging (present in unhealthy responses)
|
|
checks:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
enum: [healthy, unhealthy]
|
|
|
|
ReadinessResponse:
|
|
type: object
|
|
required:
|
|
- ready
|
|
properties:
|
|
ready:
|
|
type: boolean
|
|
services:
|
|
type: object
|
|
additionalProperties:
|
|
type: boolean
|
|
|
|
# User Schemas
|
|
UserRegistration:
|
|
type: object
|
|
required:
|
|
- phone
|
|
- password
|
|
- role
|
|
- name
|
|
- age
|
|
properties:
|
|
phone:
|
|
type: string
|
|
description: Phone number in E.164 format
|
|
example: "+8613800138000"
|
|
password:
|
|
type: string
|
|
format: password
|
|
minLength: 8
|
|
description: Password (min 8 chars, must include uppercase, lowercase, number)
|
|
role:
|
|
type: string
|
|
enum: [child, guardian, parent, teacher, psychologist, administrator]
|
|
name:
|
|
type: string
|
|
description: Full name
|
|
age:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 18
|
|
description: Age (for children)
|
|
consent_token:
|
|
type: string
|
|
description: Parental consent token (required for age <14)
|
|
|
|
UserResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- phone
|
|
- role
|
|
- name
|
|
- created_at
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: User UUID
|
|
phone:
|
|
type: string
|
|
role:
|
|
type: string
|
|
enum: [child, guardian, parent, teacher, psychologist, administrator]
|
|
name:
|
|
type: string
|
|
age:
|
|
type: integer
|
|
avatar_url:
|
|
type: string
|
|
format: uri
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
UserUpdateRequest:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
avatar_url:
|
|
type: string
|
|
format: uri
|
|
password:
|
|
type: string
|
|
format: password
|
|
|
|
# Authentication Schemas
|
|
LoginRequest:
|
|
type: object
|
|
required:
|
|
- phone
|
|
- password
|
|
properties:
|
|
phone:
|
|
type: string
|
|
password:
|
|
type: string
|
|
format: password
|
|
|
|
LoginResponse:
|
|
type: object
|
|
required:
|
|
- access_token
|
|
- token_type
|
|
- expires_in
|
|
properties:
|
|
access_token:
|
|
type: string
|
|
description: JWT access token
|
|
token_type:
|
|
type: string
|
|
example: "bearer"
|
|
expires_in:
|
|
type: integer
|
|
description: Token validity in seconds (604800 = 7 days)
|
|
refresh_token:
|
|
type: string
|
|
description: Refresh token for obtaining new access token
|
|
user:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
|
|
# Risk Assessment Schemas
|
|
RiskAssessmentRequest:
|
|
type: object
|
|
required:
|
|
- user_id
|
|
- assessment_type
|
|
properties:
|
|
user_id:
|
|
type: string
|
|
assessment_type:
|
|
type: string
|
|
enum: [comprehensive, voice_only, text_only, behavioral_only]
|
|
data_sources:
|
|
type: array
|
|
items:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
|
|
RiskAssessmentResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- user_id
|
|
- risk_score
|
|
- risk_category
|
|
- created_at
|
|
properties:
|
|
id:
|
|
type: string
|
|
user_id:
|
|
type: string
|
|
risk_score:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 100
|
|
risk_category:
|
|
type: string
|
|
enum: [low, medium, high, critical]
|
|
risk_factors:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
category:
|
|
type: string
|
|
enum: [depression, anxiety, self_harm, social_withdrawal, academic_stress, family_conflict]
|
|
severity:
|
|
type: string
|
|
enum: [low, moderate, high, critical]
|
|
confidence:
|
|
type: number
|
|
format: float
|
|
minimum: 0
|
|
maximum: 1
|
|
recommendations:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
resource_id:
|
|
type: string
|
|
title:
|
|
type: string
|
|
type:
|
|
type: string
|
|
enum: [video, audio, article, interactive_tool]
|
|
alerts_triggered:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
recipient_role:
|
|
type: string
|
|
enum: [guardian, parent, teacher, psychologist]
|
|
notification_method:
|
|
type: string
|
|
enum: [sms, wechat, push, phone_call]
|
|
sent_at:
|
|
type: string
|
|
format: date-time
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
RiskAssessmentList:
|
|
type: object
|
|
required:
|
|
- items
|
|
- total
|
|
- page
|
|
- page_size
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/RiskAssessmentResponse'
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
page_size:
|
|
type: integer
|
|
|
|
# Data Collection Schemas
|
|
VoiceAnalysisResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- user_id
|
|
- status
|
|
properties:
|
|
id:
|
|
type: string
|
|
user_id:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum: [processing, completed, failed]
|
|
estimated_completion:
|
|
type: string
|
|
format: date-time
|
|
results:
|
|
type: object
|
|
properties:
|
|
emotions:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
emotion:
|
|
type: string
|
|
confidence:
|
|
type: number
|
|
format: float
|
|
speech_rate:
|
|
type: number
|
|
format: float
|
|
description: Words per minute
|
|
dialect:
|
|
type: string
|
|
description: Detected dialect
|
|
dialect_confidence:
|
|
type: number
|
|
format: float
|
|
|
|
TextDataRequest:
|
|
type: object
|
|
required:
|
|
- user_id
|
|
- text
|
|
properties:
|
|
user_id:
|
|
type: string
|
|
text:
|
|
type: string
|
|
maxLength: 10000
|
|
context:
|
|
type: string
|
|
enum: [chat_message, diary_entry, homework_comment, other]
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
|
|
TextAnalysisResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- user_id
|
|
- sentiment
|
|
- created_at
|
|
properties:
|
|
id:
|
|
type: string
|
|
user_id:
|
|
type: string
|
|
sentiment:
|
|
type: string
|
|
enum: [positive, neutral, negative]
|
|
sentiment_score:
|
|
type: number
|
|
format: float
|
|
minimum: -1
|
|
maximum: 1
|
|
emotions:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
emotion:
|
|
type: string
|
|
confidence:
|
|
type: number
|
|
format: float
|
|
risk_keywords:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
keyword:
|
|
type: string
|
|
category:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# Resource Schemas
|
|
ResourceResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- title
|
|
- type
|
|
- category
|
|
properties:
|
|
id:
|
|
type: string
|
|
title:
|
|
type: string
|
|
description:
|
|
type: string
|
|
type:
|
|
type: string
|
|
enum: [video, audio, article, interactive_tool]
|
|
category:
|
|
type: string
|
|
enum: [depression, anxiety, self_harm, social_withdrawal, academic_stress, family_conflict]
|
|
age_group:
|
|
type: string
|
|
enum: [elementary, middle_school, high_school]
|
|
duration_seconds:
|
|
type: integer
|
|
description: Duration (for video/audio)
|
|
url:
|
|
type: string
|
|
format: uri
|
|
thumbnail_url:
|
|
type: string
|
|
format: uri
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
ResourceList:
|
|
type: object
|
|
required:
|
|
- items
|
|
- total
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ResourceResponse'
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
page_size:
|
|
type: integer
|
|
|
|
# Communication Schemas
|
|
ConversationCreateRequest:
|
|
type: object
|
|
required:
|
|
- participant_ids
|
|
properties:
|
|
participant_ids:
|
|
type: array
|
|
items:
|
|
type: string
|
|
minItems: 2
|
|
title:
|
|
type: string
|
|
|
|
ConversationResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- participants
|
|
- created_at
|
|
properties:
|
|
id:
|
|
type: string
|
|
title:
|
|
type: string
|
|
participants:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
last_message:
|
|
type: object
|
|
properties:
|
|
content:
|
|
type: string
|
|
sent_at:
|
|
type: string
|
|
format: date-time
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
ConversationList:
|
|
type: object
|
|
required:
|
|
- items
|
|
- total
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/ConversationResponse'
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
page_size:
|
|
type: integer
|
|
|
|
MessageRequest:
|
|
type: object
|
|
required:
|
|
- content
|
|
properties:
|
|
content:
|
|
type: string
|
|
maxLength: 5000
|
|
message_type:
|
|
type: string
|
|
enum: [text, image, voice]
|
|
default: text
|
|
|
|
MessageResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- conversation_id
|
|
- sender_id
|
|
- content
|
|
- sent_at
|
|
properties:
|
|
id:
|
|
type: string
|
|
conversation_id:
|
|
type: string
|
|
sender_id:
|
|
type: string
|
|
sender_name:
|
|
type: string
|
|
content:
|
|
type: string
|
|
message_type:
|
|
type: string
|
|
enum: [text, image, voice]
|
|
sent_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
MessageList:
|
|
type: object
|
|
required:
|
|
- items
|
|
- total
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/MessageResponse'
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
page_size:
|
|
type: integer
|
|
|
|
ConsultationRequest:
|
|
type: object
|
|
required:
|
|
- psychologist_id
|
|
- preferred_time
|
|
properties:
|
|
psychologist_id:
|
|
type: string
|
|
preferred_time:
|
|
type: string
|
|
format: date-time
|
|
notes:
|
|
type: string
|
|
maxLength: 1000
|
|
|
|
ConsultationResponse:
|
|
type: object
|
|
required:
|
|
- id
|
|
- child_id
|
|
- psychologist_id
|
|
- scheduled_time
|
|
- status
|
|
properties:
|
|
id:
|
|
type: string
|
|
child_id:
|
|
type: string
|
|
psychologist_id:
|
|
type: string
|
|
scheduled_time:
|
|
type: string
|
|
format: date-time
|
|
duration_minutes:
|
|
type: integer
|
|
default: 50
|
|
status:
|
|
type: string
|
|
enum: [scheduled, in_progress, completed, cancelled]
|
|
video_call_url:
|
|
type: string
|
|
format: uri
|
|
notes:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# Analytics Schemas
|
|
DashboardResponse:
|
|
type: object
|
|
required:
|
|
- role
|
|
- data
|
|
properties:
|
|
role:
|
|
type: string
|
|
enum: [child, guardian, parent, teacher, psychologist, administrator]
|
|
data:
|
|
type: object
|
|
additionalProperties: true
|
|
description: Role-specific dashboard data
|
|
|
|
responses:
|
|
BadRequest:
|
|
description: Bad request (validation error)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
Unauthorized:
|
|
description: Unauthorized (missing or invalid token)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
Forbidden:
|
|
description: Forbidden (insufficient permissions)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
NotFound:
|
|
description: Resource not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
InternalServerError:
|
|
description: Internal server error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|