User Preferences API
⚙️ User Preferences Management
The User Preferences API provides comprehensive preference management for personalizing the Quantbot experience. Users can configure investment profiles, communication preferences, watchlists, and analysis settings through a robust set of endpoints.
Base URL
All preference endpoints are prefixed with:
Authentication
All preference endpoints require user authentication. Include the Bearer token in the Authorization header:
Data Models
UserPreferencesCreate
Request model for creating comprehensive user preferences.
{
"experience_level": "intermediate",
"investment_style": "long-term",
"risk_tolerance": "moderate",
"brokerage_platform": "fidelity",
"investment_types": ["stocks", "etfs", "options"],
"analysis_depth": "detailed",
"information_sources": [
{"type": "earnings", "priority": 1},
{"type": "news", "priority": 2},
{"type": "analyst", "priority": 3}
],
"market_sectors": ["technology", "healthcare", "finance"],
"geographic_focus": "us",
"daily_summary_time": "morning",
"alert_frequency": "daily",
"esg_importance": "somewhat",
"explanation_style": "key-points",
"watchlist": ["AAPL", "MSFT", "GOOGL", "AMZN"],
"privacy_level": "limited"
}
UserPreferencesUpdate
Request model for updating existing preferences (all fields optional).
{
"risk_tolerance": "aggressive",
"watchlist": ["AAPL", "MSFT", "TSLA", "NVDA", "META"],
"alert_frequency": "real-time",
"analysis_depth": "technical"
}
UserPreferencesResponse
Complete user preferences response format.
{
"id": 123,
"user_id": 456,
"experience_level": "intermediate",
"investment_style": "long-term",
"risk_tolerance": "moderate",
"brokerage_platform": "fidelity",
"investment_types": ["stocks", "etfs", "options"],
"analysis_depth": "detailed",
"information_sources": [
{"type": "earnings", "priority": 1},
{"type": "news", "priority": 2},
{"type": "analyst", "priority": 3}
],
"market_sectors": ["technology", "healthcare", "finance"],
"geographic_focus": "us",
"daily_summary_time": "morning",
"alert_frequency": "daily",
"esg_importance": "somewhat",
"explanation_style": "key-points",
"watchlist": ["AAPL", "MSFT", "GOOGL", "AMZN"],
"privacy_level": "limited",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T14:30:00Z"
}
InformationSource
Information source preference with priority ranking.
Valid Types: earnings
, news
, analyst
, social
, technical
, economic
Priority Range: 1-6 (1 = highest priority)
BulkPreferencesUpdate
Bulk update model for wizard-based preference setting.
{
"step_data": {
"essential": {
"experience_level": "beginner",
"investment_style": "long-term",
"risk_tolerance": "conservative"
},
"portfolio": {
"investment_types": ["stocks", "etfs"],
"brokerage_platform": "robinhood"
},
"information": {
"analysis_depth": "simple",
"information_sources": [
{"type": "news", "priority": 1},
{"type": "earnings", "priority": 2}
]
}
},
"complete": true
}
Endpoints
Get User Preferences
Retrieve the current user's preferences.
Response
Status Code: 200 OK
{
"id": 123,
"user_id": 456,
"experience_level": "intermediate",
"investment_style": "long-term",
"risk_tolerance": "moderate",
"brokerage_platform": "fidelity",
"investment_types": ["stocks", "etfs", "options"],
"analysis_depth": "detailed",
"information_sources": [
{"type": "earnings", "priority": 1},
{"type": "news", "priority": 2}
],
"market_sectors": ["technology", "healthcare"],
"geographic_focus": "us",
"daily_summary_time": "morning",
"alert_frequency": "daily",
"esg_importance": "somewhat",
"explanation_style": "key-points",
"watchlist": ["AAPL", "MSFT", "GOOGL"],
"privacy_level": "limited",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T14:30:00Z"
}
Error Responses
404 Not Found - No preferences found for user
Create User Preferences
Create new preferences for the current user.
Request Body
{
"experience_level": "beginner",
"investment_style": "long-term",
"risk_tolerance": "conservative",
"brokerage_platform": "robinhood",
"investment_types": ["stocks", "etfs"],
"analysis_depth": "simple",
"information_sources": [
{"type": "news", "priority": 1},
{"type": "earnings", "priority": 2}
],
"market_sectors": ["technology"],
"geographic_focus": "us",
"daily_summary_time": "evening",
"alert_frequency": "daily",
"esg_importance": "unsure",
"explanation_style": "step-by-step",
"watchlist": ["AAPL", "MSFT"],
"privacy_level": "full"
}
Response
Status Code: 201 Created
{
"id": 124,
"user_id": 456,
"experience_level": "beginner",
"investment_style": "long-term",
"risk_tolerance": "conservative",
"brokerage_platform": "robinhood",
"investment_types": ["stocks", "etfs"],
"analysis_depth": "simple",
"information_sources": [
{"type": "news", "priority": 1},
{"type": "earnings", "priority": 2}
],
"market_sectors": ["technology"],
"geographic_focus": "us",
"daily_summary_time": "evening",
"alert_frequency": "daily",
"esg_importance": "unsure",
"explanation_style": "step-by-step",
"watchlist": ["AAPL", "MSFT"],
"privacy_level": "full",
"created_at": "2025-01-15T15:00:00Z",
"updated_at": "2025-01-15T15:00:00Z"
}
Error Responses
409 Conflict - Preferences already exist for user
Update User Preferences
Update existing preferences for the current user.
Request Body
{
"risk_tolerance": "aggressive",
"watchlist": ["AAPL", "TSLA", "NVDA", "AMD"],
"alert_frequency": "real-time",
"analysis_depth": "technical"
}
Response
Status Code: 200 OK
{
"id": 123,
"user_id": 456,
"experience_level": "intermediate",
"investment_style": "long-term",
"risk_tolerance": "aggressive",
"brokerage_platform": "fidelity",
"investment_types": ["stocks", "etfs", "options"],
"analysis_depth": "technical",
"information_sources": [
{"type": "earnings", "priority": 1},
{"type": "news", "priority": 2}
],
"market_sectors": ["technology", "healthcare"],
"geographic_focus": "us",
"daily_summary_time": "morning",
"alert_frequency": "real-time",
"esg_importance": "somewhat",
"explanation_style": "key-points",
"watchlist": ["AAPL", "TSLA", "NVDA", "AMD"],
"privacy_level": "limited",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T16:00:00Z"
}
Error Responses
404 Not Found - No preferences found to update
Delete User Preferences
Delete all preferences for the current user.
Response
Status Code: 204 No Content
Get Smart Defaults
Get intelligent default preferences based on experience level.
Path Parameters
Parameter | Type | Description |
---|---|---|
experience_level |
string | User experience level: beginner , intermediate , or advanced |
Response
Status Code: 200 OK
{
"experience_level": "beginner",
"investment_style": "long-term",
"risk_tolerance": "conservative",
"brokerage_platform": null,
"investment_types": ["stocks", "etfs"],
"analysis_depth": "simple",
"information_sources": [
{"type": "news", "priority": 1},
{"type": "earnings", "priority": 2},
{"type": "analyst", "priority": 3}
],
"market_sectors": ["technology", "healthcare", "finance"],
"geographic_focus": "us",
"daily_summary_time": "evening",
"alert_frequency": "daily",
"esg_importance": "unsure",
"explanation_style": "step-by-step",
"watchlist": [],
"privacy_level": "full"
}
Error Responses
400 Bad Request - Invalid experience level
Get Preference Choices
Get all available choices for preference fields.
Response
Status Code: 200 OK
{
"experience_level": ["beginner", "intermediate", "advanced"],
"investment_style": ["long-term", "active", "swing", "day"],
"risk_tolerance": ["conservative", "moderate", "aggressive"],
"investment_types": ["stocks", "etfs", "mutual_funds", "bonds", "options", "futures", "crypto", "real_estate"],
"analysis_depth": ["simple", "detailed", "technical"],
"market_sectors": ["technology", "healthcare", "finance", "energy", "consumer_discretionary", "consumer_staples", "industrials", "materials", "utilities", "real_estate", "telecommunications"],
"geographic_focus": ["us", "us-international", "global"],
"daily_summary_time": ["morning", "lunch", "evening", "night", "convenient"],
"alert_frequency": ["real-time", "hourly", "daily", "weekly"],
"esg_importance": ["very", "somewhat", "not", "unsure"],
"explanation_style": ["step-by-step", "key-points", "bottom-line-first"],
"privacy_level": ["full", "limited", "minimal"],
"information_source_types": ["earnings", "news", "analyst", "social", "technical", "economic"],
"brokerage_platforms": ["fidelity", "schwab", "vanguard", "etrade", "td_ameritrade", "robinhood", "webull", "interactive_brokers", "merrill", "other"]
}
Bulk Update from Wizard
Update preferences from wizard step data.
Request Body
{
"step_data": {
"essential": {
"experience_level": "intermediate",
"investment_style": "active",
"risk_tolerance": "moderate"
},
"portfolio": {
"investment_types": ["stocks", "etfs", "options"],
"brokerage_platform": "fidelity"
},
"information": {
"analysis_depth": "detailed",
"information_sources": [
{"type": "earnings", "priority": 1},
{"type": "technical", "priority": 2},
{"type": "news", "priority": 3}
],
"market_sectors": ["technology", "healthcare"]
},
"communication": {
"daily_summary_time": "morning",
"alert_frequency": "hourly"
},
"advanced": {
"esg_importance": "somewhat",
"explanation_style": "key-points",
"privacy_level": "limited"
}
},
"complete": true
}
Response
Status Code: 200 OK
Returns the complete updated preferences in UserPreferencesResponse
format.
Get or Create with Defaults
Get existing preferences or create with smart defaults.
Path Parameters
Parameter | Type | Description |
---|---|---|
experience_level |
string | Experience level for defaults: beginner , intermediate , or advanced |
Response
Status Code: 200 OK
Returns existing preferences if they exist, otherwise creates new preferences with intelligent defaults and returns them.
Update Watchlist
Update only the user's stock watchlist.
Request Body
Response
Status Code: 200 OK
Returns the complete updated preferences with the new watchlist.
Validation Rules
- Maximum 50 symbols
- Valid ticker format: 1-5 uppercase letters
- No duplicates allowed
- Automatic uppercase conversion
Error Responses
400 Bad Request - Validation error
400 Bad Request - Invalid tickers
Preference Categories
Core Investment Profile
- Experience Level:
beginner
,intermediate
,advanced
- Investment Style:
long-term
,active
,swing
,day
- Risk Tolerance:
conservative
,moderate
,aggressive
Portfolio Information
- Investment Types: Multiple selection from stocks, ETFs, options, etc.
- Brokerage Platform: User's primary trading platform
- Market Sectors: Preferred sectors for focus and analysis
Analysis & Information
- Analysis Depth:
simple
,detailed
,technical
- Information Sources: Prioritized list of preferred data sources
- Geographic Focus:
us
,us-international
,global
Communication & Alerts
- Daily Summary Time:
morning
,lunch
,evening
,night
,convenient
- Alert Frequency:
real-time
,hourly
,daily
,weekly
- Explanation Style:
step-by-step
,key-points
,bottom-line-first
Advanced Settings
- ESG Importance:
very
,somewhat
,not
,unsure
- Privacy Level:
full
,limited
,minimal
- Watchlist: Personal stock ticker symbol list
Integration Examples
Python Example
import requests
from typing import Dict, List, Any, Optional
class QuantbotPreferencesClient:
def __init__(self, base_url: str, access_token: str):
self.base_url = base_url
self.headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
def get_preferences(self) -> Dict[str, Any]:
"""Get current user preferences"""
response = requests.get(
f"{self.base_url}/api/v1/preferences/",
headers=self.headers
)
response.raise_for_status()
return response.json()
def create_preferences(self, preferences: Dict[str, Any]) -> Dict[str, Any]:
"""Create new user preferences"""
response = requests.post(
f"{self.base_url}/api/v1/preferences/",
headers=self.headers,
json=preferences
)
response.raise_for_status()
return response.json()
def update_preferences(self, updates: Dict[str, Any]) -> Dict[str, Any]:
"""Update existing preferences"""
response = requests.put(
f"{self.base_url}/api/v1/preferences/",
headers=self.headers,
json=updates
)
response.raise_for_status()
return response.json()
def get_smart_defaults(self, experience_level: str) -> Dict[str, Any]:
"""Get intelligent defaults for experience level"""
response = requests.get(
f"{self.base_url}/api/v1/preferences/defaults/{experience_level}",
headers=self.headers
)
response.raise_for_status()
return response.json()
def get_preference_choices(self) -> Dict[str, List[str]]:
"""Get available choices for all preference fields"""
response = requests.get(
f"{self.base_url}/api/v1/preferences/choices",
headers=self.headers
)
response.raise_for_status()
return response.json()
def update_watchlist(self, watchlist: List[str]) -> Dict[str, Any]:
"""Update only the watchlist"""
response = requests.patch(
f"{self.base_url}/api/v1/preferences/watchlist",
headers=self.headers,
json=watchlist
)
response.raise_for_status()
return response.json()
def bulk_update_from_wizard(self, step_data: Dict[str, Any], complete: bool = True) -> Dict[str, Any]:
"""Bulk update from wizard steps"""
response = requests.post(
f"{self.base_url}/api/v1/preferences/wizard",
headers=self.headers,
json={
"step_data": step_data,
"complete": complete
}
)
response.raise_for_status()
return response.json()
# Usage examples
prefs_client = QuantbotPreferencesClient("http://localhost:8000", "your_access_token")
# Get available choices for form building
choices = prefs_client.get_preference_choices()
print("📋 Available investment types:", choices["investment_types"])
# Get smart defaults for a beginner
defaults = prefs_client.get_smart_defaults("beginner")
print(f"🎯 Beginner defaults: {defaults['risk_tolerance']} risk, {defaults['analysis_depth']} analysis")
# Create comprehensive preferences
new_preferences = {
"experience_level": "intermediate",
"investment_style": "long-term",
"risk_tolerance": "moderate",
"brokerage_platform": "fidelity",
"investment_types": ["stocks", "etfs", "options"],
"analysis_depth": "detailed",
"information_sources": [
{"type": "earnings", "priority": 1},
{"type": "news", "priority": 2},
{"type": "analyst", "priority": 3}
],
"market_sectors": ["technology", "healthcare"],
"geographic_focus": "us",
"daily_summary_time": "morning",
"alert_frequency": "daily",
"esg_importance": "somewhat",
"explanation_style": "key-points",
"watchlist": ["AAPL", "MSFT", "GOOGL"],
"privacy_level": "limited"
}
try:
created_prefs = prefs_client.create_preferences(new_preferences)
print(f"✅ Created preferences for user {created_prefs['user_id']}")
except requests.HTTPError as e:
if e.response.status_code == 409:
print("📝 Preferences already exist, updating instead...")
updated_prefs = prefs_client.update_preferences({
"risk_tolerance": "aggressive",
"alert_frequency": "real-time"
})
print(f"✏️ Updated preferences: {updated_prefs['risk_tolerance']} risk")
# Quick watchlist update
new_watchlist = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA", "NVDA"]
updated_prefs = prefs_client.update_watchlist(new_watchlist)
print(f"📈 Updated watchlist: {len(updated_prefs['watchlist'])} symbols")
# Wizard-style bulk update
wizard_data = {
"essential": {
"experience_level": "advanced",
"investment_style": "active",
"risk_tolerance": "aggressive"
},
"information": {
"analysis_depth": "technical",
"information_sources": [
{"type": "technical", "priority": 1},
{"type": "earnings", "priority": 2}
]
},
"communication": {
"alert_frequency": "real-time",
"daily_summary_time": "morning"
}
}
wizard_prefs = prefs_client.bulk_update_from_wizard(wizard_data)
print(f"🧙♂️ Wizard update completed: {wizard_prefs['experience_level']} level")
TypeScript Example
interface UserPreferences {
id?: number;
user_id?: number;
experience_level: string;
investment_style: string;
risk_tolerance: string;
brokerage_platform?: string;
investment_types: string[];
analysis_depth: string;
information_sources: InformationSource[];
market_sectors: string[];
geographic_focus: string;
daily_summary_time: string;
alert_frequency: string;
esg_importance?: string;
explanation_style: string;
watchlist: string[];
privacy_level: string;
created_at?: string;
updated_at?: string;
}
interface InformationSource {
type: string;
priority: number;
}
interface WizardStepData {
[stepName: string]: Record<string, any>;
}
class QuantbotPreferencesAPI {
constructor(private baseURL: string, private accessToken: string) {}
private get headers() {
return {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
};
}
async getPreferences(): Promise<UserPreferences> {
const response = await fetch(`${this.baseURL}/api/v1/preferences/`, {
method: 'GET',
headers: this.headers
});
if (!response.ok) {
throw new Error(`Failed to get preferences: ${response.statusText}`);
}
return await response.json();
}
async createPreferences(preferences: Partial<UserPreferences>): Promise<UserPreferences> {
const response = await fetch(`${this.baseURL}/api/v1/preferences/`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify(preferences)
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to create preferences: ${error.detail}`);
}
return await response.json();
}
async updatePreferences(updates: Partial<UserPreferences>): Promise<UserPreferences> {
const response = await fetch(`${this.baseURL}/api/v1/preferences/`, {
method: 'PUT',
headers: this.headers,
body: JSON.stringify(updates)
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to update preferences: ${error.detail}`);
}
return await response.json();
}
async getSmartDefaults(experienceLevel: string): Promise<UserPreferences> {
const response = await fetch(
`${this.baseURL}/api/v1/preferences/defaults/${experienceLevel}`,
{
method: 'GET',
headers: this.headers
}
);
if (!response.ok) {
throw new Error(`Failed to get defaults: ${response.statusText}`);
}
return await response.json();
}
async getPreferenceChoices(): Promise<Record<string, string[]>> {
const response = await fetch(`${this.baseURL}/api/v1/preferences/choices`, {
method: 'GET',
headers: this.headers
});
if (!response.ok) {
throw new Error(`Failed to get choices: ${response.statusText}`);
}
return await response.json();
}
async updateWatchlist(watchlist: string[]): Promise<UserPreferences> {
const response = await fetch(`${this.baseURL}/api/v1/preferences/watchlist`, {
method: 'PATCH',
headers: this.headers,
body: JSON.stringify(watchlist)
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to update watchlist: ${error.detail}`);
}
return await response.json();
}
async bulkUpdateFromWizard(stepData: WizardStepData, complete: boolean = true): Promise<UserPreferences> {
const response = await fetch(`${this.baseURL}/api/v1/preferences/wizard`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
step_data: stepData,
complete
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to update from wizard: ${error.detail}`);
}
return await response.json();
}
async deletePreferences(): Promise<void> {
const response = await fetch(`${this.baseURL}/api/v1/preferences/`, {
method: 'DELETE',
headers: this.headers
});
if (!response.ok) {
throw new Error(`Failed to delete preferences: ${response.statusText}`);
}
}
}
// Usage examples
const prefsAPI = new QuantbotPreferencesAPI('http://localhost:8000', 'your_access_token');
// Comprehensive preference management
async function setupUserPreferences() {
try {
// Get available choices for form validation
const choices = await prefsAPI.getPreferenceChoices();
console.log('📋 Available risk levels:', choices.risk_tolerance);
// Try to get existing preferences
let preferences: UserPreferences;
try {
preferences = await prefsAPI.getPreferences();
console.log('📖 Found existing preferences');
} catch (error) {
// Create new preferences with smart defaults
const defaults = await prefsAPI.getSmartDefaults('intermediate');
const newPreferences: Partial<UserPreferences> = {
...defaults,
brokerage_platform: 'fidelity',
watchlist: ['AAPL', 'MSFT', 'GOOGL', 'AMZN'],
market_sectors: ['technology', 'healthcare']
};
preferences = await prefsAPI.createPreferences(newPreferences);
console.log('✅ Created new preferences with defaults');
}
// Update specific preferences
const updates = await prefsAPI.updatePreferences({
risk_tolerance: 'aggressive',
alert_frequency: 'real-time',
analysis_depth: 'technical'
});
console.log('✏️ Updated preferences:', {
risk: updates.risk_tolerance,
alerts: updates.alert_frequency,
analysis: updates.analysis_depth
});
// Update watchlist separately
const newWatchlist = ['AAPL', 'MSFT', 'NVDA', 'TSLA', 'AMD', 'GOOGL'];
const watchlistUpdate = await prefsAPI.updateWatchlist(newWatchlist);
console.log(`📈 Updated watchlist: ${watchlistUpdate.watchlist.length} symbols`);
return preferences;
} catch (error) {
console.error('❌ Preference setup failed:', error);
throw error;
}
}
// Wizard-based setup
async function setupFromWizard() {
const wizardData: WizardStepData = {
essential: {
experience_level: 'advanced',
investment_style: 'active',
risk_tolerance: 'aggressive'
},
portfolio: {
investment_types: ['stocks', 'options', 'futures'],
brokerage_platform: 'interactive_brokers'
},
information: {
analysis_depth: 'technical',
information_sources: [
{ type: 'technical', priority: 1 },
{ type: 'earnings', priority: 2 },
{ type: 'news', priority: 3 }
],
market_sectors: ['technology', 'finance']
},
communication: {
daily_summary_time: 'morning',
alert_frequency: 'real-time'
},
advanced: {
esg_importance: 'not',
explanation_style: 'bottom-line-first',
privacy_level: 'minimal'
}
};
try {
const preferences = await prefsAPI.bulkUpdateFromWizard(wizardData, true);
console.log('🧙♂️ Wizard setup completed successfully');
console.log(`🎯 Profile: ${preferences.experience_level} ${preferences.investment_style} investor`);
console.log(`⚡ Alerts: ${preferences.alert_frequency} ${preferences.analysis_depth} analysis`);
return preferences;
} catch (error) {
console.error('❌ Wizard setup failed:', error);
throw error;
}
}
Smart Defaults System
The preferences API includes an intelligent defaults system that provides appropriate starting configurations based on user experience level:
Beginner Defaults
- Risk Tolerance: Conservative
- Analysis Depth: Simple explanations
- Investment Types: Stocks and ETFs only
- Alert Frequency: Daily summaries
- Explanation Style: Step-by-step guidance
Intermediate Defaults
- Risk Tolerance: Moderate
- Analysis Depth: Detailed analysis
- Investment Types: Stocks, ETFs, and options
- Alert Frequency: Daily with real-time for major events
- Explanation Style: Key points summary
Advanced Defaults
- Risk Tolerance: Aggressive
- Analysis Depth: Technical analysis
- Investment Types: Full range including futures and crypto
- Alert Frequency: Real-time alerts
- Explanation Style: Bottom-line first approach
Validation Rules
Watchlist Validation
- Maximum 50 ticker symbols
- 1-5 uppercase letters only
- No duplicate symbols allowed
- Automatic uppercase conversion
Information Sources
- Each type can only appear once
- Priority values must be unique (1-6)
- All priorities within valid range
Investment Types
- Must be from approved list
- Multiple selections allowed
- Validated against current market offerings
Experience Level Progression
- Users can update experience level over time
- System suggests preference updates when level changes
- Smart migration of existing settings
Security Considerations
Data Privacy
- User Isolation: Each user can only access their own preferences
- Privacy Levels: Users control how much data is shared with analytics
- Audit Trail: All preference changes are logged with timestamps
Validation & Sanitization
- Input Validation: All preference values validated against allowed choices
- SQL Injection Prevention: All database queries use parameterized statements
- XSS Protection: All user inputs sanitized before storage
Error Handling
Common Error Responses
400 Bad Request - Validation error
401 Unauthorized - Authentication required
404 Not Found - Preferences not found
409 Conflict - Preferences already exist
422 Unprocessable Entity - Invalid request format
{
"detail": [
{
"loc": ["body", "risk_tolerance"],
"msg": "string does not match regex",
"type": "value_error.str.regex"
}
]
}
Related APIs
- Users API - User account management and profile information
- Chat API - Personalized chat responses based on preferences
- Authentication API - Required for all preference operations