A research tool for collecting data on budget allocation preferences using multiple algorithmic strategies. Users create ideal budget allocations, then compare pairs of alternatives to understand decision-making patterns.
Documentation:
- See the
docs/threshold_detection_research/directory for research papers and supporting materials related to threshold detection and survey methodology.
git clone https://github.com/ariel-research/budget-survey
cd budget-survey
./scripts/deploy.sh dev→ Open http://localhost:5000
- Users allocate a 100-unit budget across subjects (e.g., Health: 60, Education: 25, Defense: 15)
- System generates 10 comparison pairs using research algorithms
- Users choose their preference from each pair
- Data reveals decision-making patterns and algorithmic preferences
This is a research application for studying budget allocation preferences. It's designed for academic research into algorithmic decision-making and preference revelation.
The Process:
- 📝 Budget Creation: Users allocate 100 units across subjects (e.g., government ministries)
- 🔄 Pair Generation: System creates comparison pairs using one of 9 research strategies
- 🎯 Preference Collection: Users choose between alternatives in each pair
- 📊 Analysis: Data reveals patterns in decision-making and strategy effectiveness
Key Features:
- 9 research-validated pair generation strategies
- Hebrew/English bilingual support with RTL/LTR layouts
- Quality control via attention checks and user blacklisting
- Comprehensive analysis and PDF report generation
- Production-ready Docker deployment
Use Cases:
- Academic research on preference revelation
- Policy research on budget allocation
- Algorithm testing and validation
- User experience studies
- Python 3.8+
- MySQL 8.0+
- pip
- virtualenv
- Docker 20.10+
- Docker Compose 2.0+
- Git
-
Clone the repository:
git clone https://github.com/ariel-research/budget-survey cd budget-survey -
Create and activate a virtual environment:
python -m venv venv source venv/bin/activate -
Install dependencies:
pip install -r requirements.txt -
Set up the MySQL database (see Database section below)
-
Create a
.envfile in the project root and add the necessary environment variables (see.env.examplefor reference)
-
Clone the repository:
git clone https://github.com/ariel-research/budget-survey cd budget-survey -
Copy environment file and configure:
cp .env.example .env # Edit .env with your preferred settings -
Start the development environment:
# Using deployment script (Recommended) ./scripts/deploy.sh dev # Or manual command (use 'docker compose' for v2 or 'docker-compose' for v1) docker compose -f docker-compose.dev.yml up -d
-
Access the application at: http://localhost:5000
-
Activate the virtual environment (if not already activated)
-
Run the Flask application using either of these commands:
python app.pyThis will run the application on port 5001.
or
flask runThis will run the application on port 5000.
-
Access the application based on the command used:
- If using
python app.py: http://localhost:5001 - If using
flask run: http://localhost:5000
- If using
# Development
./scripts/deploy.sh dev # Start everything (recommended)
docker compose -f docker-compose.dev.yml logs -f app # View application logs
docker compose -f docker-compose.dev.yml exec app bash # Access application shell
docker compose -f docker-compose.dev.yml down # Stop everything
# Analysis & Reports
python -m analysis.survey_analysis # Run survey analysis
python -m analysis.survey_report_generator_pdf # Generate PDF report
open https://survey.csariel.xyz/report # View live report
# Testing
pytest # Run all tests
pytest tests/services/ # Test strategies
pytest tests/analysis/ # Test analysis
pytest tests/api/ # Test endpoints
# Configuration
SURVEY_ID = 4 # Change active survey (edit config.py)
curl http://localhost:5001/health # Check application health
docker compose -f docker-compose.dev.yml ps # View application statusThe application includes an automatic rescaling feature that helps users create valid budget allocations:
-
Purpose: Helps users adjust their budget allocations to:
- Sum to exactly 100
- Ensure all numbers are divisible by 5
- Maintain relative proportions between departments
-
How it works:
- Proportionally adjusts non-zero values to sum to 100
- Rounds each value to the nearest multiple of 5
- Makes final adjustments to ensure the total remains exactly 100
- Maintains a minimum value of 5 for any non-zero allocation
- Preserves zero allocations (does not rescale them)
-
Button States: The "Rescale" button becomes disabled when:
- The total sum is already exactly 100
- All values are zero
- Any input contains invalid numbers
- The total is zero
-
Constraints:
- Requires at least two departments with non-zero allocations
- Maintains relative proportions between original values as closely as possible while satisfying the constraints
Users can trigger rescaling at any time using the "Rescale" button in the budget allocation interface.
The application uses the Strategy pattern to support multiple pair generation algorithms. Each survey can be configured with its own pair generation strategy.
-
L1 vs. Leontief Comparison
- Strategy name:
l1_vs_leontief_comparison - Generates pairs that force users to choose between minimizing sum of differences and maximizing minimal ratio
- Each pair contains two non-ideal allocations where one is better in terms of sum of differences while the other is better in terms of minimal ratio
- Parameters:
num_pairs: Number of pairs to generate (default: 10)
- Example:
# User's ideal: (60, 20, 20) # Option 1: (40, 30, 30) Better minimal ratio (0.67) but worse sum of differences (40) # Option 2: (70, 15, 15) Better sum of differences (20) but worse minimal ratio (0.75)
- Strategy name:
-
Star-Shaped Preference Test
- Strategy name:
star_shaped_preference_test - Generates pairs by combining user's ideal vector with random vectors using weighted averages
- Each pair contains:
- A random vector different from user's ideal allocation
- A weighted combination of the random vector and user's ideal allocation
- Weighting pattern:
- Starts with 10% user vector, 90% random vector
- Gradually increases user vector weight by 10% each round
- Includes two pairs at 50-50 weight
- Ends with 100% user vector weight
- Parameters:
num_pairs: Number of pairs to generate (default: 10)
- Example:
# For user_vector = [20, 30, 50]: # Round 1: x=0.1, y=0.9 # - Random vector: [40, 40, 20] # - Weighted result: [38, 39, 23] (40*0.9 + 20*0.1, 40*0.9 + 30*0.1, 20*0.9 + 50*0.1)
- Strategy name:
-
Star-Shaped Preference Test (Rounded)
- Strategy name:
star_shaped_preference_test_rounded - Extends the Weighted Vector Strategy to ensure all allocations are multiples of 5
- Each pair contains:
- A random vector different from user's ideal allocation (in multiples of 5)
- A weighted combination rounded to multiples of 5
- Maintains all weighting patterns from the parent strategy
- Parameters:
num_pairs: Number of pairs to generate (default: 10)
- Example:
# For user_vector = [60, 25, 15]: # With x_weight = 0.3: # - Random vector: [30, 45, 25] # - Before rounding: [39, 39, 22] (30*0.7 + 60*0.3, 45*0.7 + 25*0.3, 25*0.7 + 15*0.3) # - After rounding to multiples of 5: [40, 40, 20]
- Strategy name:
-
L1 vs. L2 Comparison
- Strategy name:
l1_vs_l2_comparison - Compares root of sum of squared differences vs regular sum of differences
- Each pair contains two non-ideal allocations where one is better in terms of root sum squared differences while the other is better in terms of regular sum of differences
- Parameters:
num_pairs: Number of pairs to generate (default: 10)
- Example:
# User's ideal: (50, 25, 25) # Option 1: (25, 70, 5) Better root sum squared (55.23) but worse sum differences (90) # Option 2: (10, 25, 65) Better sum differences (80) but worse root sum squared (56.57)
- Strategy name:
-
L2 vs. Leontief Comparison
- Strategy name:
l2_vs_leontief_comparison - Compares root of sum of squared differences vs minimal ratio
- Each pair contains two non-ideal allocations where one is better in terms of root sum squared differences while the other is better in terms of minimal ratio
- Parameters:
num_pairs: Number of pairs to generate (default: 10)
- Example:
# User's ideal: (60, 25, 15) # Option 1: (50, 30, 20) Better minimal ratio (0.83) but worse root sum squared (12.25) # Option 2: (65, 25, 10) Better root sum squared (7.07) but worse minimal ratio (0.67)
- Strategy name:
-
Peak-Linearity Test
-
Strategy name:
peak_linearity_test -
Tests user preferences between extreme allocations and their weighted combinations with the ideal vector
-
Generates two types of pairs:
- Extreme vector pairs: Each extreme vector allocates 100% to one department
- Weighted average pairs: Combines user's ideal vector with extreme vectors using weights of 25%, 50%, and 75%
-
Parameters:
num_pairs: Number of pairs to generate (default: 9)
-
Example:
# For vector_size=3 and user_vector = [70, 20, 10]: # Extreme pairs: # [100, 0, 0] vs [0, 100, 0] # [100, 0, 0] vs [0, 0, 100] # [0, 100, 0] vs [0, 0, 100] # Weighted average pairs (50% weight): # [85, 10, 5] vs [35, 60, 5] (weighted averages with [100,0,0] and [0,100,0])
-
Purpose: Tests the hypothesis that if a user prefers extreme vector A over extreme vector B, they will also prefer weighted averages that incorporate extreme vector A over those with extreme vector B
-
Analysis Features:
- Core Preference Analysis: Shows the user's fundamental preferences between extreme vectors (A vs B, A vs C, B vs C)
- Percentile Breakdown Table: Displays consistency metrics for different weight percentiles (25%, 50%, 75%)
- Shows how well user choices align with their core preferences at different weight levels
- Helps identify if consistency varies with the "extremeness" of the choices
- Provides separate consistency scores for each comparison group (A vs B, A vs C, B vs C)
- Includes an "All Percentiles" summary row with overall consistency metrics
-
For peak_linearity_test surveys, the system analyzes logical consistency:
- Groups analyzed: Core vectors, 25%, 50%, 75% weighted
- Transitivity check: If A>B and B>C, then A>C must hold
- Metrics:
- Preference order per group (e.g., A>B>C)
- Transitivity status (✓/✗)
- Overall transitivity rate (0-100%)
- Order Consistency score (consistency of order across groups)
Note: '>' represents observed choice, which may include cases of user indifference.
-
Component Symmetry Test
- Strategy name:
component_symmetry_test - Generates 12 comparison pairs organized into 4 groups using cyclic shifts of difference vectors
-
Algorithm Overview:
- Creates 4 groups of 3 pairs each (total 12 pairs)
- Each group starts with two independent random difference vectors that sum to zero
- Applies cyclic shifts (0, 1, 2 positions) to these difference vectors
- Adds the shifted differences to the user's ideal vector to create comparison options
-
Difference Vector Generation:
- Generates two independent random difference vectors
- Both vectors sum to zero to maintain budget constraints
- Ensures vectors are canonically different (sorted patterns differ)
- Each vector must have at least one meaningful difference (|diff| >= 5)
- Validates that resulting budget allocations remain within [0, 100] range
-
Validation Improvements:
- Uses absolute canonical form validation to prevent degenerate pairs
- Ensures difference vectors are not absolute canonical identical
- Maintains research validity by avoiding equivalent patterns
- Guarantees perfect mathematical relationships without rounding approximations
-
Cyclic Shift Logic:
- Applies right shifts of 0, 1, and 2 positions to create 3 pairs per group
- Each shift moves difference elements to the right by the specified positions
- Elements that exceed array bounds wrap around to the beginning
-
Special Handling:
- Automatically detects when user's ideal allocation contains zero values
- Throws
UnsuitableForStrategyErrorif zeros are present, as cyclic shifts could create invalid budget allocations - Ensures all generated allocations maintain valid budget constraints
- Applies multiples-of-5 rounding when appropriate
- Parameters:
-
num_pairs: Number of pairs to generate (default: 12, always generates exactly 12)
-
- Example:
# For user_vector = [20, 30, 50]: # Group 1 - Generate two independent difference vectors: # diff1 = [-10, +5, +5], diff2 = [+20, -25, +5] # Pair 1 (shift 0): Apply differences directly # Option A: [20, 30, 50] + [-10, +5, +5] = [10, 35, 55] # Option B: [20, 30, 50] + [+20, -25, +5] = [40, 5, 55] # Pair 2 (shift 1): Shift differences right by 1 position # diff1_shifted = [+5, -10, +5], diff2_shifted = [+5, +20, -25] # Option A: [20, 30, 50] + [+5, -10, +5] = [25, 20, 55] # Option B: [20, 30, 50] + [+5, +20, -25] = [25, 50, 25] # Pair 3 (shift 2): Shift differences right by 2 positions # diff1_shifted = [+5, +5, -10], diff2_shifted = [-5, +15, +10] # Option A: [20, 30, 50] + [+5, +5, -10] = [25, 35, 40] # Option B: [20, 30, 50] + [-5, +15, +10] = [15, 45, 60] # Groups 2-4 repeat this process with different random difference vectors
- Strategy name:
-
Sign Symmetry Test
- Strategy name:
sign_symmetry_test - Generates 12 comparison pairs organized into 6 groups to test linear symmetry hypothesis
- Core Hypothesis: Tests whether users treat positive and negative distances from their ideal allocation as equivalent
-
Algorithm Overview:
- Creates 6 groups of 2 pairs each (total 12 pairs)
- Each group generates two distance vectors v1 and v2 that sum to zero
- Creates pair A: (ideal + v1) vs (ideal + v2)
- Creates pair B: (ideal - v1) vs (ideal - v2)
-
Distance Vector Generation:
- Generates two independent distance vectors that sum to zero
- Neither vector can be all zeros
- Vectors must be different from each other
- Each vector must have at least one meaningful difference (|diff| >= 5)
- Validates that both addition and subtraction produce valid budget allocations within [0, 100]
-
Validation Improvements:
- Uses absolute canonical form validation to prevent degenerate pairs
- Ensures distance vectors are not absolute canonical identical
- Maintains research validity by avoiding equivalent patterns
- Guarantees perfect mathematical symmetry without rounding approximations
-
Linear Symmetry Logic:
- Tests if users view distance D and distance -D as equivalent
- If symmetry hypothesis holds, users should show similar preference patterns for both pairs in each group
- Positive distances (ideal + v) represent movement in one direction from ideal
- Negative distances (ideal - v) represent movement in opposite direction
-
Special Handling:
- Automatically detects when user's ideal allocation contains zero values
- Throws
UnsuitableForStrategyErrorif zeros are present, as distance calculations could create invalid budget allocations - Ensures all generated allocations maintain valid budget constraints
- Applies multiples-of-5 rounding when appropriate
- Parameters:
-
num_pairs: Number of pairs to generate (default: 12, always generates exactly 12)
-
- Example:
# For user_vector = [40, 30, 30]: # v1 = [15, -10, -5], v2 = [-10, 5, 5] # Group 1: # Pair A (positive distances): [55, 20, 25] vs [30, 35, 35] (ideal + v1 vs ideal + v2) # Pair B (negative distances): [25, 40, 35] vs [50, 25, 25] (ideal - v1 vs ideal - v2) # Groups 2-6 repeat this process with different distance vectors
-
Analysis Features:
- Linear Consistency Analysis: Measures how consistently users treat positive and negative distances
- Group-level consistency metrics showing symmetry adherence
- Helps identify if users have directional biases in budget allocation preferences
- Strategy name:
-
Asymmetric Loss Distribution Strategy
- Strategy name:
asymmetric_loss_distribution - Tests user preferences between concentrated vs. distributed budget changes using a calibrated-magnitude approach.
-
Algorithm Overview:
- Generates 12 pairs based on a "calibrated-magnitude" approach.
base_unit = max(1, round(min(ideal_budget) / 10))- Four magnitude levels are tested for each of the 3 budget categories.
-
Comparison Types:
- Type A (Primary): Concentrated loss vs. Distributed loss.
- Type B (Fallback): Concentrated funding vs. Distributed funding.
-
Special Handling:
- Throws
UnsuitableForStrategyErrorif the user's ideal budget contains any zero values.
- Throws
-
Analysis Features:
- Preference Consistency: Measures overall preference for distributed vs. concentrated changes.
- Magnitude Sensitivity: Analyzes how the magnitude of the change affects user preferences.
- Type A vs. Type B Patterns: Compares user choices in the primary test vs. the fallback scenario.
- Strategy name:
-
Preference Ranking Survey Strategy
- Strategy name:
preference_ranking_survey - Tests user preference order through forced-ranking methodology instead of pairwise comparisons
-
Algorithm Overview:
- Generates 4 forced-ranking questions (5 questions including 1 awareness check at position 3)
- Each question presents 3 budget options that users rank from most to least preferred
- Converts rankings to 12 comparison pairs for analysis (4 questions × 3 pairs each)
- Uses two magnitude levels: X1 = max(1, round(0.2 × min(user_vector))), X2 = max(1, round(0.4 × min(user_vector)))
-
Question Generation:
- Creates base difference vectors: Positive (+2X, -X, -X) and Negative (-2X, +X, +X)
- Generates 3 options per question using base vector and 2 cyclic shifts
- 4 questions test: X1_positive, X1_negative, X2_positive, X2_negative
-
Special Handling:
- Ranking-based interface: Users rank 3 options instead of choosing between 2
- Includes awareness check at position 3 where user's ideal allocation is Option B
- Throws
UnsuitableForStrategyErrorif user's ideal budget contains any zero values - Only supports 3-subject budget allocations (vector_size=3)
- Parameters:
-
num_pairs: Number of pairs to generate (default: 12, always generates exactly 12)
-
- Example:
# For user_vector = [60, 25, 15]: # X1 = max(1, round(0.2 × 15)) = 3, X2 = max(1, round(0.4 × 15)) = 6 # Question 1 (X1_positive): base_diff = [6, -3, -3] # Option A: [60, 25, 15] + [6, -3, -3] = [66, 22, 12] # Option B: [60, 25, 15] + [-3, 6, -3] = [57, 31, 12] (shift 1) # Option C: [60, 25, 15] + [-3, -3, 6] = [57, 22, 21] (shift 2) # User ranks: A > B > C (creates 3 pairs: A vs B, A vs C, B vs C)
- Core Hypothesis: User's underlying preference order will be consistently revealed across all ranking questions, providing insights into stable budget allocation priorities
- Strategy name:
-
Dynamic Temporal Preference Test
- Strategy name:
biennial_budget_preference - Tests comprehensive temporal discounting and preference for achieving exact ideal states versus mathematically balanced two-year plans
-
Algorithm Overview:
- Generates 12 pairs across three 4-question sub-surveys
- Uses balanced vector pairs (B, C) where (B+C)/2 = user's ideal vector
- Implements two-tiered vector generation with fallback mechanism
-
Sub-Surveys:
- Simple Discounting (4 questions): (Ideal, Random) vs (Random, Ideal) - tests basic temporal discounting
- Second-Year Choice (4 questions): (B, Ideal) vs (B, C) - Year 1 budget fixed as B, user chooses Year 2 budget
- First-Year Choice (4 questions): (Ideal, B) vs (C, B) - Year 2 budget fixed as B, user chooses Year 1 budget
- Core Hypothesis: Users exhibit temporal discounting while also preferring exact ideal states over balanced mathematical plans
-
Analysis Features:
- Dynamic Temporal Preference Summary: Shows percentage breakdown for all three sub-surveys
- Sub-survey specific analysis: S1 (Ideal Year 1), S2 (Ideal Year 2), S3 (Ideal Year 1)
- Robust vector generation with multiples-of-5 constraint and fallback mechanism
-
Vector Generation:
- Primary attempt: Generate balanced pairs where all values are multiples of 5
- Fallback attempt: Generate pairs without multiples-of-5 constraint for extreme vectors
- Error handling: Raises
UnsuitableForStrategyErrorif unable to generate sufficient pairs
-
Special Handling:
- Throws
UnsuitableForStrategyErrorif the user's ideal budget contains any zero values, as this can prevent the generation of a sufficient number of valid balanced vector pairs.
- Throws
- Parameters:
-
num_pairs: Number of pairs to generate (must be 12)
-
- Example:
# For user_vector = [60, 30, 10]: # Sub-Survey 1: Simple Discounting # Pair 1: ((60,30,10), (45,25,30)) vs ((45,25,30), (60,30,10)) # Ideal Year 1 vs Ideal Year 2 # Sub-Survey 2: Second-Year Choice (B=[50,35,15], C=[70,25,5]) # Pair 5: ((50,35,15), (60,30,10)) vs ((50,35,15), (70,25,5)) # B,Ideal vs B,C (Year 1 fixed as B) # Sub-Survey 3: First-Year Choice # Pair 9: ((60,30,10), (50,35,15)) vs ((70,25,5), (50,35,15)) # Ideal,B vs C,B (Year 2 fixed as B)
- Strategy name:
-
Triangle Inequality Test
- Strategy name:
triangle_inequality_test - Tests if user preferences for biennial budgets satisfy the triangle inequality by comparing concentrated versus distributed budget changes over two years.
-
Algorithm Overview:
- Generates 12 pairs to test if the perceived "distance" of a budget change satisfies
||q|| ≤ ||q1|| + ||q2||, whereqis a total change vector decomposed intoq1andq2. - Each pair compares a concentrated change (full deviation
qin Year 2) with a distributed change (deviation split intoq1in Year 1 andq2in Year 2). - Structure: 2 base change vectors × 3 cyclic rotations × 2 variants (positive/negative) = 12 pairs.
- All comparisons are for biennial budgets, represented as flattened 6-element vectors.
- Generates 12 pairs to test if the perceived "distance" of a budget change satisfies
-
Special Handling:
- Throws
UnsuitableForStrategyErrorif the user's ideal budget contains any zero values, as this can create invalid budget allocations.
- Throws
- Parameters:
-
num_pairs: Number of pairs to generate (must be 12).
-
- Example:
# For user_vector p = [50, 30, 20] # Base change q = [10, -20, 10], decomposed into: # q1 = [10, 0, -10] and q2 = [0, -20, 20] # Positive Variant Pair: # Concentrated Option (Year 1: p, Year 2: p+q): # ([50, 30, 20], [60, 10, 30]) # Distributed Option (Year 1: p+q1, Year 2: p+q2): # ([60, 30, 10], [50, 10, 40])
-
Core Hypothesis & Analysis:
-
Hypothesis: This test assumes the user has an additive utility model (where total disutility is the sum of each year's disutility), as two initial screening questions filter out participants who "balance" budgets across years. The hypothesis is that this additive model, when treated as a distance, satisfies the triangle inequality (
$||q|| \le ||q_1|| + ||q_2||$ ). Therefore, users should consistently prefer the "Concentrated" option (one large deviation, total disutility$||q||$ ) over the "Distributed" option (two smaller deviations, total disutility$||q_1|| + ||q_2||$ ), because its total disutility is lower (or equal). -
Analysis Features:
- Measures user preference for concentrated vs. distributed changes.
- Calculates a "Triangle Inequality Consistency" score to see how choices align with the mathematical principle.
-
Hypothesis: This test assumes the user has an additive utility model (where total disutility is the sum of each year's disutility), as two initial screening questions filter out participants who "balance" budgets across years. The hypothesis is that this additive model, when treated as a distance, satisfies the triangle inequality (
- Strategy name:
-
Multi-Dimensional Single-Peaked Test
- Strategy name:
multi_dimensional_single_peaked_test - Generates pairs where the "near" option is weakly closer to the user's peak on every dimension and strictly closer on at least one, ensuring consistent directional deviations.
- Rejects user vectors containing zeros and filters out duplicate or peak-identical candidates to keep comparisons informative.
- Example:
# User's ideal: (50, 30, 20) # Option A (Far): (50, 40, 10) # Deviations: [0, +10, -10] # Option B (Near): (50, 35, 15) # Deviations: [0, +5, -5]
- Rank-Based Comparison Strategies
- Strategy names:
l1_vs_leontief_rank_comparison,l1_vs_l2_rank_comparison,l2_vs_leontief_rank_comparison,leontief_vs_anti_leontief_rank_comparison,leontief_vs_kl_rank_comparison,kl_vs_anti_leontief_rank_comparison - Uses rank-based normalization (percentiles) instead of raw values to generate pairs with optimal trade-offs.
- How it works:
- Enumerates a discrete simplex grid (default step=5).
- Enforces a
min_component(e.g., 10%) so every category gets a realistic minimum budget. - Normalizes utility models to percentile ranks (0.0-1.0) to eliminate scale differences.
- Uses a Max-Min search to find pairs where one vector is significantly better on Utility Model A, while the other is better on Utility Model B.
- Utility Models Supported:
- L1 (Sum): Total absolute disagreement.
- Leontief (Ratio): Minimal satisfaction ratio (fairness).
- L2 (RSS): Euclidean distance (penalizes extreme outliers).
- Anti-Leontief: Penalizes over-funding (waste aversion).
- Kullback-Leibler (KL): Information theoretic divergence (asymmetric penalty for losses).
The application uses a Strategy Pattern with Polymorphic Utility Models. To add a new rank-based strategy:
- Define a Utility Model (if new): Add a class to
application/services/algorithms/utility_models.pyinheriting fromUtilityModel. - Create Strategy: In
application/services/pair_generation/rank_strategies.py, create a thin wrapper:class MyNewRankStrategy(GenericRankStrategy): def __init__(self, grid_step=None): super().__init__(UtilityModelA, UtilityModelB, min_component=10)
- Register: Add
StrategyRegistry.register(MyNewRankStrategy)inapplication/services/pair_generation/__init__.py.
Each strategy can define its own table columns for displaying survey response statistics by implementing the get_table_columns() method. This allows the system to dynamically generate strategy-specific tables based on the strategy used for each survey.
For examples of how to configure surveys to use different strategies, see the Adding or Modifying Surveys section.
The application provides comprehensive bilingual support:
-
Available Languages:
- Hebrew (default)
- English
-
Key Features:
- Language switcher in the UI header
- Automatic RTL layout for Hebrew
- LTR layout for English
- Language preference persistence across sessions
- Fallback to Hebrew for missing translations
-
Translation Coverage:
- User interface elements
- Error messages
- Survey questions and instructions
- Survey subjects (e.g., ministry names)
- System messages and alerts
- Button labels and tooltips
- Form validations
- Success/failure notifications
-
How to Switch Languages:
- Via UI: Click the language toggle in the top-right corner
- Via URL: Add 'lang' parameter to the URL
- For Hebrew:
?lang=he - For English:
?lang=en - Example:
https://survey.csariel.xyz/?userID=abc&surveyID=123&lang=en
- For Hebrew:
- Selection is remembered for future visits
- Can be changed at any point during the survey
All translations are managed through the translations system, making it easy to maintain and update content in both languages.
The application includes an attention check mechanism to ensure survey quality:
-
Purpose: Validate user attention during survey completion
-
Implementation:
- Two attention check questions mixed within comparison pairs
- Validates that users recognize their own optimal allocation
- Failed checks are recorded and do not allow retries
-
Early awareness failures: Client-side checks catch wrong awareness answers before submission, store
pts_valueinsurvey_responsesas awareness codes (1= first question failed,2= second question failed), and redirect users to the survey company. If per-survey PTS tokens are configured (surveys.awareness_pts), the redirect includes the matching token; otherwise the redirect omits thePTSparam but still sends the status. -
Panel4All (External survey provider) Integration:
- Sends "attentionfilter" status for failed attention checks
- Sends "finish" status for successful completions
- Uses per-survey PTS tokens when available. Falls back to status-only redirect if tokens are missing
-
Data Storage:
- Failed checks are stored with
attention_check_failedflag - Maintains data for analysis while excluding from main results
- Supports research on survey response quality
- Failed checks are stored with
The application includes a user blacklist feature to automatically enforce quality control in survey responses.
-
Purpose: Enhance data quality by preventing participation from users who fail to meet attention standards
-
Implementation:
- Users who fail attention checks are automatically blacklisted from taking future surveys
- Blacklist status is checked during survey eligibility verification
- Blacklisted users are redirected to a dedicated explanation page
- System records which survey triggered the blacklist
-
Data Storage:
- Blacklist information is stored in the users table:
blacklistedflag indicates blacklist statusblacklisted_atrecords when user was blacklistedfailed_survey_ididentifies which survey triggered the blacklist
- Indexed for efficient lookups during eligibility checks
- Blacklist information is stored in the users table:
The application includes a 'Demo Mode' feature that allows users to explore the survey functionality without affecting the actual data.
-
Purpose: Provides a sandbox environment for users to familiarize themselves with the survey process.
-
How it works:
- Users can enable 'Demo Mode'.
- In 'Demo Mode', all interactions are simulated, and no data is stored permanently.
- Users can navigate through the survey, make allocations, and submit responses as they would in a real survey.
- The system provides feedback and results based on the simulated data.
-
Limitations:
- Data generated in 'Demo Mode' is not saved to the database.
- Some features may be restricted to prevent misuse.
This feature is ideal for training sessions and demonstrations, allowing users to experience the full functionality of the application without impacting real survey data.
The application provides a comprehensive dashboard showing participation statistics for all users who have completed surveys.
-
Purpose: Monitor user engagement and survey completion patterns
-
Key Features:
- Shows successful and failed survey counts per user
- Color-coded clickable survey IDs (green for successful, red for failed)
- Sortable by User ID or Last Activity
- Accessible via dashboard metric card or direct URL:
/surveys/users - Full bilingual support with RTL/LTR layouts
- Navigation to Performance Matrix for detailed metrics
-
Data Displayed: User IDs, survey counts, last activity timestamps, and direct links to individual responses
The application provides a detailed matrix view showing strategy-specific performance metrics for each user-survey combination.
-
Purpose: Analyze user performance patterns across different survey strategies
-
Key Features:
- Matrix format with users as rows and surveys as columns
- Strategy-specific metric labels (e.g., "Random / Weighted Average", "Sum / Ratio")
- Sticky User ID column for easy navigation
- Full bilingual support with RTL/LTR layouts
- Accessible via direct URL:
/surveys/users/matrix
-
Data Displayed: Performance metrics for each user-survey combination, with "-" indicating no participation
The application features an automated, maintenance-free system for assigning consistent colors to survey strategy badges.
- Automatic Coloring: New strategies added to the database automatically receive a unique, deterministic color based on their name hash. No manual CSS updates are required.
- Algorithm: Uses MD5 hashing with a prime multiplier to generate a consistent HSL Hue (0-360), with fixed Saturation (65%) and Lightness (40%) to ensure WCAG accessibility compliance against white text.
- Smart Formatting: Raw database keys (e.g.,
l1_vs_leontief_comparison) are automatically formatted into human-readable titles (e.g., "L1 Vs Leontief Comparison") in the UI. - Layout Safety: Badges are CSS-truncated to a single line with an ellipsis (
...) to prevent grid breakage, while a native tooltip provides the full text on hover.
Implementation Details:
- Backend:
strategy_colorJinja2 filter inapp.py. - Frontend: Inline styles in
surveys_overview.htmlanddetail.html. - Data Preservation: Semantic
data-strategyattributes are preserved in the DOM for automated testing reliability.
The application uses a MySQL database with multilingual support. Here's the schema:
You can set up the database using one of two methods:
-
Connect to your MySQL server using the MySQL client.
-
Create a new database:
CREATE DATABASE survey;
-
Use the newly created database:
USE survey;
-
Run the SQL commands from the database/schema.sql file to create the necessary tables and structure:
-
Ensure you have Docker and Docker Compose installed on your system.
-
Navigate to the project root directory.
-
Start the database using the development environment:
# Start full development environment (recommended) ./scripts/deploy.sh dev # Or just the database docker compose -f docker-compose.dev.yml up -d db
This will create a MySQL container, create the database, and run the initialization script (database/schema.sql) to set up the necessary tables and structure.
Note: Make sure your .env file is properly configured with the correct database connection details before running either method.
- Run in order:
python migrations/run_migration.py migrations/20251204_add_pts_value_column.sqlpython migrations/run_migration.py migrations/20251204_add_survey_response_uniqueness.sql
- Note: The uniqueness migration will fail if duplicates already exist for
(user_id, survey_id); clean them first if needed.
Note: This guide uses
docker compose(v2) syntax. If you have the older standalonedocker-compose(v1), replacedocker composewithdocker-composein the commands below. The deployment script automatically detects and uses the correct version.
-
Copy environment file:
cp .env.example .env
-
Start development environment:
./scripts/deploy.sh dev
-
Common development commands:
# View logs docker compose -f docker-compose.dev.yml logs -f app # Run tests docker compose -f docker-compose.dev.yml exec app pytest # Access shell docker compose -f docker-compose.dev.yml exec app bash # Stop docker compose -f docker-compose.dev.yml down
Prerequisites:
- Ubuntu 20.04+ EC2 instance
- Domain name
- Docker installed
Installation:
# Install Docker on EC2
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# Install Docker Compose v2 (recommended)
# Docker Compose v2 comes built-in with Docker Desktop
# For Ubuntu/Debian server installations:
sudo apt-get install docker-compose-plugin
sudo rebootDeployment:
# Clone and configure
git clone https://github.com/ariel-research/budget-survey
cd budget-survey
cp .env.example .env
# Edit .env with production settings:
# FLASK_ENV=production
# FLASK_SECRET_KEY=your-secret-key
# MYSQL_PASSWORD=secure-password
# SURVEY_BASE_URL=https://your-domain.com
# Setup SSL
sudo apt install certbot
sudo certbot certonly --standalone -d your-domain.com
mkdir -p ssl
sudo cp /etc/letsencrypt/live/your-domain.com/fullchain.pem ssl/cert.pem
sudo cp /etc/letsencrypt/live/your-domain.com/privkey.pem ssl/key.pem
sudo chown $USER:$USER ssl/*
# Deploy
./scripts/deploy.sh prodManagement:
# Update application
git pull && docker compose -f docker-compose.prod.yml up -d --build
# View logs
docker compose -f docker-compose.prod.yml logs -f
# Backup database
docker compose -f docker-compose.prod.yml exec db mysqldump -u root -p survey > backup.sqlCreate .env file with these required variables:
# Application
FLASK_ENV=development|production
FLASK_SECRET_KEY=your-secret-key
APP_PORT=5001
# Database
MYSQL_HOST=db # Use 'db' for Docker, 'localhost' for local
MYSQL_DATABASE=survey
MYSQL_USER=survey_user
MYSQL_PASSWORD=secure_password
# Survey
SURVEY_BASE_URL=http://localhost:5001|https://your-domain.comCommon issues:
# Container won't start
docker compose logs [service_name]
# Database connection issues
docker compose logs db
docker compose restart db
# Port already in use
sudo lsof -i :5001
# Change APP_PORT in .env
# Permission issues
sudo chown -R $USER:$USER .Health check:
curl http://localhost:5001/health-
Dashboard (Main Landing Page)
/- Analytics dashboard (Main landing page)
-
Survey Taking
/take-survey/?userID=...&surveyID=...- Take survey (with default survey ID)/take-survey/?userID=...&surveyID=...&internalID=N- Take survey with specific internal ID/take-survey/create_vector- Create budget allocation/take-survey/survey- Compare budget pairs/take-survey/thank_you- Survey completion page/take-survey/?userID=...&surveyID=...&demo=true- Take survey in Demo Mode
-
Analysis & Reports
/report- View survey analysis report (PDF)- Automatically refreshes based on latest data
- Updates CSVs and PDF as needed
- Shows PDF in browser with download option
/dev/report- Development report for testing- Always generates fresh report
- Useful for testing template changes
- Creates 'survey_analysis_report_dev.pdf'
-
Survey Results
/surveys/responses- All survey responses/surveys/{survey_id}/responses- Responses for specific survey- With filtering:
/surveys/{survey_id}/responses?view_filter=v_users_preferring_weighted_vectors - Other filters:
v_users_preferring_rounded_weighted_vectors,v_users_preferring_any_weighted_vectors
- With filtering:
/surveys/users- User Participation Overview- Sortable by User ID or Last Activity
- Shows successful/failed survey counts per user
- Color-coded clickable survey IDs
/surveys/users/matrix- User-Survey Performance Matrix- Shows strategy-specific metrics for each user-survey combination
- Displays performance data across all surveys in a matrix format
/surveys/users/{user_id}/responses- All responses from specific user/surveys/{survey_id}/users/{user_id}/responses- User's response to specific survey/surveys/comments- All user comments/surveys/{survey_id}/comments- Comments for specific survey
/get_messages- Returns JSON dictionary of error messages
Notes:
- For survey taking endpoints, both 'userID' and 'surveyID' parameters are required
- The 'surveyID' parameter in the URL is required but not used internally
- The survey ID is determined by either:
- Custom internal survey ID via
internalIDparameter - Default survey ID from config file
- Custom internal survey ID via
-
Dashboard
- Dashboard (Main landing page): https://survey.csariel.xyz/
-
Survey Taking
-
Analysis & Reports
- Survey Report: https://survey.csariel.xyz/report
- Development Report: https://survey.csariel.xyz/dev/report
-
Survey Results
- All Responses: https://survey.csariel.xyz/surveys/responses
- Survey Responses: https://survey.csariel.xyz/surveys/{survey_id}/responses
- With filtering: https://survey.csariel.xyz/surveys/{survey_id}/responses?view_filter=v_users_preferring_weighted_vectors
- Other filters: v_users_preferring_rounded_weighted_vectors, v_users_preferring_any_weighted_vectors
- User Participation Overview: https://survey.csariel.xyz/surveys/users
- Sortable by User ID or Last Activity
- Shows successful/failed survey counts per user
- Color-coded clickable survey IDs
- User-Survey Performance Matrix: https://survey.csariel.xyz/surveys/users/matrix
- Shows strategy-specific metrics for each user-survey combination
- Displays performance data across all surveys in a matrix format
- User Responses: https://survey.csariel.xyz/surveys/users/{user_id}/responses
- User Survey Response: https://survey.csariel.xyz/surveys/{survey_id}/users/{user_id}/responses
- All Comments: https://survey.csariel.xyz/surveys/comments
- Survey Comments: https://survey.csariel.xyz/surveys/{survey_id}/comments
Notes:
- URL parameters required for survey taking:
- userID: Required for user identification
- surveyID: Required but not used internally
- internalID: Optional, overrides default survey ID from config
To modify the survey that users will get, you need to manually update the SURVEY_ID value in the file config.py. Look for the following line and change the number to the desired survey ID:
SURVEY_ID = 1 # Change this to the desired survey IDTo add new surveys or modify existing ones, follow these steps:
-
Connect to the database on the remote server using MySQL Workbench via SSH:
- Create a new connection
- Choose "Standard TCP/IP over SSH" as the connection method
- SSH Hostname: [your_server_address]
- SSH Username: [your_ssh_username]
- SSH Password: [your_ssh_password] (or use SSH Key File)
- MySQL Hostname: 127.0.0.1
- MySQL Server Port: 3306
- Username: [your_mysql_username]
- Password: [your_mysql_password]
-
Once connected, you can run SQL queries to add or modify surveys. Here are some example queries:
First, add a new story (if needed):
INSERT INTO stories ( code, title, description, subjects ) VALUES ( 'budget_2024', JSON_OBJECT( 'he', 'סקר תקציב 2024', 'en', 'Budget Survey 2024' ), JSON_OBJECT( 'he', 'סקר שנתי להקצאת תקציב', 'en', 'Annual budget allocation survey' ), JSON_ARRAY( JSON_OBJECT('he', 'בריאות', 'en', 'Health'), JSON_OBJECT('he', 'חינוך', 'en', 'Education'), JSON_OBJECT('he', 'ביטחון', 'en', 'Defense') ) );
Then, add a new survey that uses this story:
-- awareness_pts is optional; include it only if you have per-survey PTS tokens INSERT INTO surveys ( story_code, active, pair_generation_config ) VALUES ( 'budget_2024', TRUE, JSON_OBJECT( 'strategy', 'biennial_budget_preference', 'params', JSON_OBJECT('num_pairs', 10), 'pair_instructions', JSON_OBJECT( 'he', 'עליכם לקבוע את התקציב עבור שתי שנים עוקבות: השנה הנוכחית, והשנה הבאה. איזה מבין שני התקציבים הבאים תעדיפו שיהיה התקציב בשנה הנוכחית? התקציב שלא תבחרו יהיה התקציב בשנה הבאה.', 'en', 'You need to set the budget for two consecutive years: the current year and next year. Which of the following two budgets would you prefer to be the current year''s budget? The budget you don''t choose will be next year''s budget.' ) ) );
Modify an existing story's content:
UPDATE stories SET title = JSON_OBJECT( 'he', 'סקר תקציב מעודכן 2024', 'en', 'Updated Budget Survey 2024' ), description = JSON_OBJECT( 'he', 'סקר שנתי מעודכן להקצאת תקציב', 'en', 'Revised annual budget allocation survey' ), subjects = JSON_ARRAY( JSON_OBJECT('he', 'בריאות', 'en', 'Health'), JSON_OBJECT('he', 'חינוך', 'en', 'Education'), JSON_OBJECT('he', 'ביטחון', 'en', 'Defense') ) WHERE code = 'budget_2024';
Update just the pair generation strategy for a survey:
UPDATE surveys SET pair_generation_config = JSON_OBJECT( 'strategy', 'l1_vs_leontief_comparison', 'params', JSON_OBJECT('num_pairs', 10) ) WHERE id = 1;
Deactivate a survey:
UPDATE surveys SET active = FALSE WHERE id = 1;
Remember to:
- Use valid strategy names as defined in the pair generation strategies
- Include all required parameters for the chosen strategy
- Update the
SURVEY_IDinconfig.pyafter adding or modifying surveys - Ensure that story codes are unique across the stories table
Surveys can include custom instructions for specific strategies. These instructions are stored in the pair_instructions field within the pair_generation_config JSON object and support both Hebrew and English languages. If no custom instructions are provided, the system will not display any instructions for that survey.
INSERT INTO surveys (
-- awareness_pts is optional; include only if you have PTS tokens
story_code,
active,
pair_generation_config
)
VALUES (
'my_custom_survey',
TRUE,
JSON_OBJECT(
'strategy', 'biennial_budget_preference',
'params', JSON_OBJECT('num_pairs', 10),
'pair_instructions', JSON_OBJECT(
'he', 'הוראות מותאמות אישית בעברית',
'en', 'Custom personalized instructions in English'
)
)
);UPDATE surveys
SET pair_generation_config = JSON_SET(
pair_generation_config,
'$.pair_instructions',
JSON_OBJECT(
'he', 'הוראות מעודכנות בעברית',
'en', 'Updated instructions in English'
)
)
WHERE id = 1;To fall back to no instructions being displayed:
UPDATE surveys
SET pair_generation_config = JSON_REMOVE(
pair_generation_config,
'$.pair_instructions'
)
WHERE id = 1;Note: The pair_instructions field is optional. If omitted or set to NULL, no instructions will be displayed for that survey. This allows for flexible customization of the user experience.
You can highlight specific words or phrases in your custom instructions using HTML tags for better visual emphasis:
INSERT INTO surveys (
-- awareness_pts is optional; include only if you have PTS tokens
story_code,
active,
pair_generation_config
)
VALUES (
'highlighted_instructions_survey',
TRUE,
JSON_OBJECT(
'strategy', 'biennial_budget_preference',
'params', JSON_OBJECT('num_pairs', 10),
'pair_instructions', JSON_OBJECT(
'he', 'עליכם לקבוע את התקציב עבור <mark>שתי שנים עוקבות</mark>: <strong>השנה הנוכחית</strong>, והשנה הבאה.',
'en', 'You need to set the budget for <mark>two consecutive years</mark>: <strong>the current year</strong> and next year.'
)
)
);Supported HTML Tags:
<strong>- Bold text (emphasized text)<em>- Italic text (emphasized text)<mark>- Highlighted text (yellow background)<u>- Underlined text (underlined with primary color)
Example with multiple highlighting styles:
'pair_instructions', JSON_OBJECT(
'he', 'עליכם לקבוע את התקציב עבור <strong><mark>שתי שנים עוקבות</mark></strong>: <u>השנה הנוכחית</u>, והשנה הבאה.',
'en', 'You need to set the budget for <strong><mark>two consecutive years</mark></strong>: <u>the current year</u> and next year.'
)Security Note: Only these basic HTML tags are supported. Any other HTML/JavaScript content will be automatically escaped for security.
To change a strategy's name, you'll need to update both code and database references.
-
Update the Strategy Class:
- Open the strategy file in
application/services/pair_generation/(e.g.,optimization_metrics_vector.py) - Modify the
get_strategy_name()method:def get_strategy_name(self) -> str: """Get the unique identifier for this strategy.""" return "new_strategy_name" # Changed from original name
- Open the strategy file in
-
Update the Database:
- Update all surveys using this strategy with this SQL query:
UPDATE surveys SET pair_generation_config = JSON_REPLACE( pair_generation_config, '$.strategy', 'new_strategy_name' ) WHERE id = 1; # Replace with the survey ID
- To update multiple surveys:
UPDATE surveys SET pair_generation_config = JSON_REPLACE( pair_generation_config, '$.strategy', 'new_strategy_name' ) WHERE id IN (1, 4, 6); # List all affected survey IDs
- Update all surveys using this strategy with this SQL query:
To modify the awareness check questions in the survey:
-
Change the Question Text:
- Open
application/translations.py - Locate and modify the awareness question text in both languages:
"awareness_question": { "he": "בחר באפשרות מספר 1. זוהי שאלת בדיקת תשומת לב.", "en": "Please select option 1. This is an attention check question.", },
- Open
-
Change the Answer Options or Expected Answers:
- Open
application/services/awareness_check.py - Modify the
generate_awareness_questionsfunction to change how options are created - Open
application/schemas/validators.py - Update the validation logic in the
SurveySubmission.validate()method:# Change which answers are expected if ( len(self.awareness_answers) != 2 or self.awareness_answers[0] != 1 # First check must be 1 or self.awareness_answers[1] != 2 # Second check must be 2 ):
- Open
The core algorithm of this application is implemented in the generate_user_example function. The function generates a graph based on the user's optimal budget allocation, creating comparison pairs that optimize for both difference and ratio.
The project includes an 'analysis' package that processes the collected survey data and generates insightful statistics and reports. This package is crucial for understanding user responses and deriving meaningful conclusions from the survey data.
To run the survey analysis, use the following command from the project root directory:
python -m analysis.survey_analysis
To generate a comprehensive PDF report of the survey results, use the following command from the project root directory:
python -m analysis.survey_report_generator_pdf
This command will create a PDF report named 'survey_analysis_report.pdf' in the 'data' directory. The report includes:
- Executive summary
- Overall survey participation statistics
- Visualizations of algorithm preferences:
- Per-survey answer percentages
- User survey majority choices
- Overall majority choice distribution
- Total answer percentage distribution
- Detailed survey-wise analysis
- Individual participant analysis
- Key findings and conclusions
- Methodology description
The analysis package consists of several key components:
-
Data Retrieval and Processing:
get_all_completed_survey_responses(): Retrieves and processes all completed survey responses from the database.
-
Statistical Analysis:
generate_survey_optimization_stats(df): Generates optimization statistics for all survey responses.summarize_stats_by_survey(df): Summarizes statistics by survey ID, including a total summary row.
-
Report Generation:
generate_report(): Orchestrates the entire report generation process, including data loading, analysis, visualization, and PDF creation.- Various functions for generating specific report sections (e.g., executive summary, survey analysis, visualizations).
-
Visualization:
- Multiple functions for creating charts and graphs to visualize survey results and trends.
For a complete list of functions and their descriptions, please refer to the source code in the analysis directory.
The analysis scripts generate the following files in the data directory:
- all_completed_survey_responses.csv: Raw data of all completed survey responses.
- survey_optimization_stats.csv: Optimization statistics for each survey response.
- summarize_stats_by_survey.csv: Aggregated statistics for each survey and overall summary.
- survey_analysis_report.pdf: Comprehensive PDF report of survey results and analysis.
-
All Completed Survey Responses
- Each row represents a single comparison pair from a completed survey.
- Includes survey ID, user ID, optimal allocation, and details of each comparison pair.
-
Survey Optimization Stats
- Each row represents a completed survey response.
- Shows the number of sum-optimized and ratio-optimized choices for each response.
-
Summarize Stats by Survey
- Each row represents aggregate data for a single survey, with a final row summarizing across all surveys.
- Includes metrics such as unique users, total answers, and percentages of sum/ratio optimized choices.
Remember to regularly run both the analysis script and the report generator to keep these statistics and reports up-to-date as new survey responses are collected.
The project includes comprehensive test coverage across multiple testing domains. All tests are located in the tests/ directory.
tests/
├── analysis/ # Data analysis and reporting tests
│ ├── utils/
│ │ ├── test_analysis_utils.py
│ │ ├── test_file_utils.py
│ │ └── test_visualization_utils.py
│ ├── test_report_content_generators.py
│ ├── test_survey_analysis.py
│ └── test_survey_report_generator.py
├── api/ # API endpoint tests
│ └── test_routes.py
├── database/ # Database integration tests
│ └── test_database_integration.py
├── performance/ # Load and performance tests
│ └── load_test.py
├── services/ # Service layer tests
│ ├── pair_generation/ # Pair generation strategy tests
│ │ ├── test_cyclic_shift_strategy.py # Comprehensive validation for all 171 valid vectors
│ │ ├── test_linear_symmetry_strategy.py # Mathematical relationship verification
│ │ └── test_*.py # Other strategy tests
│ └── test_survey_vector_generator.py
├── UI/ # Frontend/UI tests
│ └── test_client_side.pyRun all tests:
pytestGitHub Actions automatically runs the full test suite on every push and pull request:
- ✅ Builds Docker containers with all dependencies
- ✅ Runs complete test suite across all categories
- ✅ Validates production build compatibility
- 📊 View results at: Actions tab
Description: Data processing and reporting
pytest tests/analysis/Description: Endpoint functionality and error handling
pytest tests/api/Description: Data persistence and integrity
pytest tests/database/Description: Core algorithms and pair generation strategies
pytest tests/services/Key features:
- Comprehensive validation: Tests all 171 valid budget vectors for algorithmic completeness
- Mathematical verification: Ensures perfect cyclic shifts and linear symmetry relationships
- Performance testing: Validates sub-3-second generation times
Description: Frontend functionality
pytest tests/UI/Description: Performance and scalability testing We use Locust for performance testing. The load tests simulate realistic user behavior patterns.
To run the load test, follow these steps:
-
Start your application server if it's not already running.
-
Run Locust in headless mode using the following command:
locust -f tests/performance/load_test.py --headless -u 100 -r 2 -t 1m --host=[your host]
Replace [your host] with the appropriate host address (e.g., http://localhost:5001).
This command does the following:
-f tests/performance/load_test.py: Specifies the Locust file to use--headless: Runs Locust in headless mode (no web UI)-u 100: Simulates 100 users-r 10: Spawns 2 users per second-t 5m: Runs the test for 1 minute--host=[your host]: Specifies the host to load test
-
Locust will run the test and output the results to the console. You'll see real-time statistics including request counts, response times, and failure rates.
-
After the test is completed, Locust will generate a summary of the test results in the console output.
Note: It's crucial to have your application server running before starting the Locust test. The load test will attempt to interact with your live application, so an active server is necessary for accurate results.
- Use the provided
.pre-commit-config.yamlfor code formatting and linting - Run tests using
pytestbefore committing changes - Database migrations are stored in
migrations/directory - Follow timestamp-based naming convention (e.g.,
20250216_add_attention_check_column.sql)
- Logs: Stored in the
logsdirectory - Data: Analysis outputs stored in
datadirectory - Migrations: Database changes in
migrations/directory - Tests: All tests in
tests/with category-based organization
To update the live application after code changes:
- Log into the server
- Inside the
appfolder:git pull - Check service status:
sudo myservice status - Restart service:
sudo myservice restart
To modify displayed text:
- Translations:
application/translations.py(Hebrew/English) - Templates:
templates/directory with translation key usage - Dynamic content: Loaded from database based on user language preference
