A Flask-based backend service that transforms math word problems (MWPs) into visual representations using the Math2Visual framework. The system generates both formal and intuitive visual representations as SVG images.
The backend consists of the following key components:
- API Routes: RESTful endpoints for generation, uploads, tutoring, analytics, and system management
- Visual Generation: Services for creating formal and intuitive SVG representations
- Language Generation: OpenAI-powered conversion of MWPs to visual language (DSL)
- Tutor Service: Gemini-powered interactive tutor with DSL-grounded guidance and streaming responses
- Analytics: Session, action, screenshot, and cursor tracking for UX insights
- Storage Management: Configurable storage backend (local/JuiceFS) for SVG datasets
- Security: Input validation, SVG sanitization, AI tutor message text sanitization, and optional ClamAV integration
backend/
βββ app/ # Main application package
β βββ __init__.py # Flask application factory
β βββ api/ # API layer
β β βββ middleware/ # Error handlers and middleware
β β β βββ __init__.py
β β β βββ error_handlers.py
β β βββ routes/ # API endpoints
β β β βββ __init__.py
β β β βββ analytics.py # Analytics and usage tracking
β β β βββ chatgpt.py # ChatGPT session endpoints (text + streaming)
β β β βββ generation.py # Core generation API
β β β βββ svg_dataset.py # SVG dataset management (upload, search, serve)
β β β βββ tutor.py # AI tutor session endpoints (text + streaming)
β β β βββ system.py # System status endpoints
β β βββ __init__.py
β βββ config/ # Configuration management
β β βββ __init__.py
β β βββ database.py # Database configuration
β β βββ storage_config.py # Storage backend configuration
β βββ models/ # Data models
β β βββ __init__.py
β β βββ chatgpt_session.py # ChatGPT session database model
β β βββ user_actions.py # User action tracking models
β β βββ tutor_session.py # Tutor session database model
β βββ services/ # Business logic
β β βββ chatgpt/ # ChatGPT session management
β β β βββ __init__.py
β β β βββ session_storage.py # ChatGPT session storage (database-backed, shared across workers)
β β βββ language_generation/ # GPT-based DSL generation
β β β βββ __init__.py
β β β βββ gpt_generator.py
β β β βββ model_generator.py
β β βββ svg_generation/ # AI-powered SVG icon generation
β β β βββ __init__.py
β β β βββ svg_generator.py # Gemini-based SVG generation
β β βββ tutor/ # Gemini-powered tutor orchestration
β β β βββ dsl_container_types.py # DSL container type utilities
β β β βββ gemini_tutor.py # Tutor session + streaming helpers
β β β βββ session_storage.py # Tutor session storage (database-backed, shared across workers)
β β βββ validation/ # Input/output validation
β β β βββ __init__.py
β β β βββ security_scanner.py # ClamAV integration
β β β βββ svg_validator.py
β β β βββ text_sanitizer.py # HTML sanitization for AI messages
β β βββ visual_generation/ # SVG generation engines
β β β βββ __init__.py
β β β βββ container_type_utils.py # Container type utilities
β β β βββ dsl_parser.py
β β β βββ formal_generator.py
β β β βββ intuitive_generator.py
β β βββ __init__.py
β βββ translations/ # Flask-Babel translation catalogs
β β βββ en/ # English translations (source language)
β β β βββ LC_MESSAGES/
β β β βββ messages.mo
β β β βββ messages.po
β β βββ de/ # German translations
β β βββ LC_MESSAGES/
β β βββ messages.mo
β β βββ messages.po
β βββ utils/ # Utility functions
β βββ __init__.py
β βββ cleanup.py # Temp/output cleanup helpers
β βββ translations.py # Argos-based term translation for SVG search
β βββ validation_constants.py # Shared validation constants
βββ app.py # Application entry point (development)
βββ wsgi.py # WSGI entry point (production, for Gunicorn)
βββ gunicorn.conf.py # Gunicorn WSGI server configuration
βββ math2visual.yml # Conda environment file
βββ babel.cfg # Babel extraction configuration
βββ messages.pot # Extracted message template (Babel)
βββ requirements.txt # Python dependencies
βββ requirements-cleanup.txt # Dependencies for cleanup script
βββ storage/ # Local storage directory
β βββ datasets/svg_dataset/ # SVG entity library (1,549 files)
β βββ models/ # ML model checkpoints
β β βββ base_model/ # Base language models
β β βββ check-point/ # Fine-tuned adapters
β βββ output/ # Generated visualizations
β βββ temp_svgs/ # Temporary AI-generated SVG icons
β βββ analytics/ # Analytics data storage
β βββ *.png # User session screenshots
β βββ heatmaps/ # Generated heatmap visualizations
βββ scripts/ # Setup and management scripts
β βββ cleanup_temp_files.py # File cleanup utility
β βββ format_juicefs.sh # JuiceFS formatting script
β βββ generate_heatmap.py # Heatmap generation from cursor analytics
β βββ install_juicefs.sh # JuiceFS installation
β βββ install_systemd_service.sh # Systemd service installation
β βββ juicefs-math2visual.service.template # Systemd service template
β βββ mount_juicefs.sh # JuiceFS mounting
β βββ setup_translations.sh # Flask-Babel translation setup
β βββ start_production.sh # Production deployment script
β βββ uninstall_systemd_service.sh # Systemd service uninstallation
β βββ verify_juicefs.sh # JuiceFS verification
βββ docs/ # Documentation
β βββ ANALYTICS_SETUP.md # Analytics stack & API setup
β βββ PRODUCTION_DEPLOYMENT.md # Production deployment guide
β βββ JUICEFS_SETUP.md # JuiceFS setup instructions
β βββ CLAMAV_SETUP.md # ClamAV antivirus setup
β βββ TRANSLATIONS.md # Backend translations with Flask-Babel
β βββ cleanup_setup.md # File cleanup documentation
βββ config_templates/ # Configuration templates
β βββ env_analytics_template # Example env for analytics DB
β βββ env_juicefs_template # JuiceFS environment template
βββ tests/ # Test suite
βββ test_svg_validator.py
- Python 3.12+
- PostgreSQL 13+ (for JuiceFS mode)
- OpenAI API key
- Optional: ClamAV for security scanning
- Clone and setup environment:
cd backend/
# Option 1: Using conda (recommended - requirements.txt is a conda environment file)
conda create --name math2visual --file requirements.txt
# Option 2: Using pip (install individual packages)
pip install flask flask-cors python-dotenv openai torch transformers peft accelerate bitsandbytes safetensors gunicorn nh3- Configure environment variables:
Update
.envfile with required environment variables.
# OpenAI Configuration
OPENAI_API_KEY=your_openai_api_key
# Gemini Configuration (SVG generation + tutor)
GEMINI_API_KEY=your_gemini_api_key
GEMINI_TUTOR_MODEL=gemini-pro-latest # optional override
# Storage Configuration
SVG_STORAGE_MODE=local # or 'juicefs'
SVG_DATASET_PATH=/path/to/svg/dataset
SVG_CACHE_SIZE=100
# Database Configuration (PostgreSQL for tutor sessions and analytics)
# Example (matches the default docker-compose configuration):
DATABASE_URL=postgresql://math2visual_user:math2visual_password@localhost:5432/math2visual_analytics
DATABASE_ECHO=false # Set to true for SQL query logging (development only)
# Tutor Session Configuration
# Inactivity-based expiration for tutor sessions (in hours). Default: 2
TUTOR_SESSION_EXPIRATION_HOURS=2
# Flask Environment (affects CORS and other behaviors)
# Options: development, production, testing
# FLASK_ENV=production
# CORS Configuration (optional - defaults work for most deployments)
# Explicit list of allowed origins (comma-separated)
# Examples:
# CORS_ORIGINS=https://your-frontend-domain.com,https://www.your-frontend-domain.com
# CORS_ORIGINS=https://app.math2visual.com
CORS_ORIGINS=
# Frontend URL (automatically determines CORS origin in production)
# Example: FRONTEND_URL=https://app.math2visual.com
FRONTEND_URL=
# CORS Security Notes:
# - In development: Allows localhost origins on common ports (3000, 5173, 8080)
# - In production: Only allows explicitly configured origins (very restrictive by default)
# - Always configure CORS_ORIGINS or FRONTEND_URL in production
# - For multiple subdomains: List them explicitly in CORS_ORIGINS
# JuiceFS Configuration (only if using JuiceFS)
See [`docs/JUICEFS_SETUP.md`](docs/JUICEFS_SETUP.md)
3. **Run the application:**
**Development mode:**
```bash
python app.pyProduction mode:
# Using the production script (recommended)
./scripts/start_production.sh
# Or directly with Gunicorn
gunicorn --config gunicorn.conf.py wsgi:appThe server will start on http://localhost:5000 by default.
For production deployment, see the comprehensive guide: docs/PRODUCTION_DEPLOYMENT.md
Generate visual representations from math word problems.
Request Body:
{
"mwp": "Janet has 9 oranges and Sharon has 7 oranges. How many oranges do they have together?",
"formula": "9 + 7 = 16" // optional
}Alternative - Direct DSL:
{
"dsl": "visual_language: addition(container1[entity_name: orange, entity_type: orange, entity_quantity: 9, container_name: Janet, container_type: girl], container2[entity_name: orange, entity_type: orange, entity_quantity: 7, container_name: Sharon, container_type: girl], result_container[entity_name: orange, entity_type: orange, entity_quantity: 16, container_name: Janet and Sharon, container_type: ])"
}Response:
{
"visual_language": "addition(...)",
"svg_formal": "<svg>...</svg>", // Base64 or SVG content
"svg_intuitive": "<svg>...</svg>", // Base64 or SVG content
"formal_error": null,
"intuitive_error": null,
"missing_svg_entities": ["entity1", "entity2"]
}Generate only the formal visualization for a given Visual Language (DSL).
Request Body:
{
"dsl": "operation(...)"
}Response:
{
"variant": "formal",
"visual_language": "operation(...)",
"svg": "<svg>...</svg>",
"error": null,
"missing_svg_entities": [],
"is_parse_error": false
}Generate only the intuitive visualization for a given Visual Language (DSL).
Request Body: Same as /api/generate/formal
Response: Same shape as /api/generate/formal with variant: "intuitive"
Upload SVG file to the svg_dataset directory with validation and security scanning.
Request Body (multipart/form-data):
file: SVG file (required)
expected_filename: string (required) - Expected filename for validation
Example using curl:
curl -X POST http://localhost:5000/api/svg-dataset/upload \
-F "file=@apple.svg" \
-F "expected_filename=apple.svg"Response (Success - New file):
{
"success": true,
"message": "SVG file 'apple.svg' uploaded successfully",
"validation_details": {
"filename_valid": true,
"size_valid": true,
"type_valid": true,
"content_valid": true,
"antivirus_scan": {
"antivirus_available": true,
"scan_performed": true,
"scanner_error": null,
"threat_found": null
}
}
}Response (Error - File already exists):
{
"success": false,
"error": "File 'apple.svg' already exists or has been added by another user in the meantime"
}Response (Error):
{
"success": false,
"error": "No file uploaded"
}Search SVG files in the dataset by name.
Query Parameters:
query: Search string to match against SVG filenames (optional)limit: Maximum number of results (default: 10, max: 50)
Response:
{
"files": [
{
"filename": "apple.svg",
"name": "apple",
"path": "/path/to/svg_dataset/apple.svg"
}
],
"query": "apple"
}Check if an SVG name already exists in the dataset.
Query Parameters:
name: SVG name to check (without extension)
Response:
{
"exists": true,
"name": "apple"
}Serve SVG files from the dataset.
Parameters:
filename: The SVG filename to serve
Response: SVG file content with appropriate headers
Generate an SVG icon using AI (Gemini) based on an entity type.
Request Body:
{
"entity_type": "apple"
}Example using curl:
curl -X POST http://localhost:5000/api/svg-dataset/generate \
-H "Content-Type: application/json" \
-d '{"entity_type": "apple"}'Response (Success):
{
"success": true,
"svg_content": "<svg>...</svg>",
"temp_filename": "temp_apple_1234567890.svg"
}Response (Error):
{
"success": false,
"error": "Entity type is required"
}Notes:
- The generated SVG is stored temporarily in
storage/temp_svgs/ - The
temp_filenamemust be used with the confirm endpoint to save permanently - Temporary SVGs are subject to automatic cleanup if not confirmed
- This endpoint uses the Gemini AI model configured via
GEMINI_API_KEY
Confirm and permanently save a previously generated SVG from the temporary storage to the dataset.
Request Body:
{
"temp_filename": "temp_apple_1234567890.svg"
}Example using curl:
curl -X POST http://localhost:5000/api/svg-dataset/confirm-generated \
-H "Content-Type: application/json" \
-d '{"temp_filename": "temp_apple_1234567890.svg"}'Response (Success):
{
"success": true,
"filename": "apple.svg"
}Response (Error - File not found):
{
"success": false,
"error": "Temporary SVG file not found"
}Response (Error - File already exists):
{
"success": false,
"error": "SVG with name 'apple' already exists in the dataset"
}Notes:
- This endpoint moves the SVG from
storage/temp_svgs/to the permanent dataset - The final filename is derived from the temporary filename (removes
temp_prefix and timestamp) - Once confirmed, the temporary file is deleted
- If a file with the same name already exists in the dataset, the operation fails
Response (Content validation error - Malicious content detected):
{
"success": false,
"error": "Content validation failed: File contains potentially malicious content: <script[^>]*>",
"validation_details": {
"filename_valid": true,
"size_valid": true,
"type_valid": true,
"content_valid": false,
"antivirus_scan": null
}
}Response (Filename validation error):
{
"success": false,
"error": "Filename validation failed: File must have .svg extension",
"validation_details": {
"filename_valid": false,
"size_valid": false,
"type_valid": false,
"content_valid": false,
"antivirus_scan": null
}
}Response (File size validation error):
{
"success": false,
"error": "File too large (max 5MB)",
"validation_details": {
"filename_valid": true,
"size_valid": false,
"type_valid": false,
"content_valid": false,
"antivirus_scan": null
}
}filename_valid: Checks for.svgextension and safe filename characterssize_valid: Verifies file size is under the 5MB limittype_valid: Validates SVG MIME type and basic structurecontent_valid: Scans for malicious patterns (scripts, external references, etc.)antivirus_scan: ClamAV scanning results when antivirus is available:antivirus_available: Whether ClamAV daemon is runningscan_performed: Whether the scan was successfully executedscanner_error: Any error message from the scannerthreat_found: Specific threat name if malware detected
ClamAV Available and Clean:
"antivirus_scan": {
"antivirus_available": true,
"scan_performed": true,
"scanner_error": null,
"threat_found": null
}ClamAV Not Available:
"antivirus_scan": {
"antivirus_available": false,
"scan_performed": false,
"scanner_error": "ClamAV daemon not running",
"threat_found": null
}Threat Detected:
"antivirus_scan": {
"antivirus_available": true,
"scan_performed": true,
"scanner_error": null,
"threat_found": "Trojan.SVG.Malware"
}Gemini-powered tutor that guides students through a math word problem using the generated Visual Language (DSL).
Start a tutoring session (generates DSL first).
Request Body:
{
"mwp": "Janet has 9 oranges and Sharon has 7 oranges. How many oranges do they have together?"
}Response:
{
"session_id": "9ad3c7a9-...",
"tutor_message": "Hi! Let's work through this together...",
"visual_language": "addition(...)",
"visual": {
"variant": "intuitive",
"svg": "<svg>...</svg>",
"error": null,
"is_parse_error": false,
"dsl_scope": "addition(...)"
}
}Start a tutoring session with a new math word problem using streaming tutor response (Server-Sent Events). Generates visual language (DSL) from the MWP first, then streams the tutor's initial response.
Request Body:
{
"mwp": "Janet has 9 oranges and Sharon has 7 oranges. How many oranges do they have together?"
}Stream Payloads:
- Chunk:
data: {"type":"chunk","delta":"..."} - Final:
data: {"type":"done","session_id":"...","tutor_message":"...","visual_language":"...","visual":{...}} - Error:
data: {"type":"error","error":"..."}
Response Format:
Server-Sent Events (SSE) stream with Content-Type: text/event-stream
Example Stream:
data: {"type":"chunk","delta":"Hi! "}
data: {"type":"chunk","delta":"Let's "}
data: {"type":"chunk","delta":"work "}
data: {"type":"chunk","delta":"through "}
data: {"type":"chunk","delta":"this "}
data: {"type":"chunk","delta":"together..."}
data: {"type":"done","session_id":"9ad3c7a9-...","tutor_message":"Hi! Let's work through this together...","visual_language":"addition(...)","visual":{"variant":"intuitive","svg":"<svg>...</svg>","error":null,"is_parse_error":false,"dsl_scope":"addition(...)"}}
Notes:
- If MWP is provided, generates visual language first, then streams the tutor conversation
- The final
donepayload includesvisual_languagefield (unlike/api/tutor/message/stream) - The
visualfield in the done payload contains the rendered visualization if a visual request was generated
Stream a tutoring reply (Server-Sent Events over a POST request).
Request Body (JSON):
{
"session_id": "9ad3c7a9-...",
"message": "I think we should add the oranges."
}Stream Payloads:
- Chunk:
data: {"type":"chunk","delta":"..."} - Final:
data: {"type":"done","session_id":"...","tutor_message":"...","visual":{...}}
Get storage configuration and status information.
Response (Success):
{
"storage": {
"cache_size": 100,
"error": null,
"is_juicefs_enabled": true,
"is_valid": true,
"juicefs_mounted": true,
"sample_file_accessible": true,
"storage_mode": "juicefs",
"svg_dataset_path": "/mnt/juicefs/svg_dataset",
"svg_file_count": 1550,
"upload_path": "/mnt/juicefs/svg_dataset"
},
"success": true
}Response (Error):
{
"success": false,
"error": "Failed to get storage status: Permission denied"
}Get antivirus scanner status and configuration information.
Response (Success):
{
"antivirus": {
"clamav_version": "ClamAV 1.0.7/27730/Tue Aug 12 10:33:28 2025",
"connection_method": "socket",
"connection_target": "/var/run/clamav/clamd.ctl",
"pyclamd_installed": true,
"scanner_available": true,
"scanner_module_available": true
},
"success": true
}
}Response (Error):
{
"success": false,
"error": "Failed to get antivirus status: ClamAV daemon not running"
}Create or update a user session.
Request Body:
{ "session_id": "client-session-id" }Response:
{
"success": true,
"session_id": "client-session-id",
"created_at": "2025-08-20T12:34:56.000Z",
"last_activity": "2025-08-20T12:34:56.000Z"
}Record multiple user actions.
Request Body (example):
{
"session_id": "client-session-id",
"actions": [
{ "type": "click", "data": { "target": "generate" }, "timestamp": "2025-08-20T12:35:00Z" }
]
}Upload a base64 PNG screenshot for a session.
Request Body (fields): session_id, image_data, width, height, optional timestamp
Record multiple cursor positions.
Request Body (example):
{
"session_id": "client-session-id",
"positions": [
{ "x": 120.5, "y": 340.2, "element_type": "button", "element_id": "generate-btn", "timestamp": "2025-08-20T12:35:05Z" }
]
}OpenAI ChatGPT integration for analytics mode chat interface. Supports streaming text and images. Note: The ChatGPT view is only available when analytics are enabled.
Start a new ChatGPT session.
Request Body:
{}Response:
{
"session_id": "9ad3c7a9-..."
}Stream a ChatGPT response using Server-Sent Events. Supports text and images.
Request Body:
{
"session_id": "9ad3c7a9-...",
"message": "Please create an image which I can use for teaching for the math word problem \"Janet has nine oranges and Sharon has seven oranges. How many oranges do Janet and Sharon have together?\"."
}Stream Payloads:
- Chunk:
data: {"type":"chunk","delta":"..."} - Final:
data: {"type":"done","session_id":"...","message":"...","images":[...]} - Error:
data: {"type":"error","error":"..."}
Response Format:
Server-Sent Events (SSE) stream with Content-Type: text/event-stream
Notes:
- The
imagesfield in the response contains URLs of generated images (if any) - ChatGPT can generate images using DALL-E 3, which are included in the response
Proxy image download to avoid CORS issues. Fetches an image from an external URL and returns it.
Query Parameters:
url: The URL of the image to proxy
Response: Returns the image file with appropriate content-type headers.
Example:
GET /api/chatgpt/proxy-image?url=https://example.com/image.pngNotes:
- Used to download images that may have CORS restrictions
- Timeout is set to 30 seconds
The system generates two types of visual representations:
- Mathematical precision with exact quantities
- Explicit mathematical operations
- Accentuates underlying relationships in a clear mathematical way
- Grid-based layouts for structured display
- Detailed, example-driven illustration of mathematical relationships to reflect authentic real-world situations and narrative contexts
- Contextual groupings / arrangements
- Designed to improve engagement & reduce the cognitive load
Both generators support:
- Dynamic SVG composition from 1,549+ entity library
- Intelligent layout calculation based on content
- Missing entity tracking for dataset expansion
- Error handling with detailed feedback
The system uses OpenAI's GPT models to convert natural language math word problems into structured visual language (DSL).
operation(
container1[entity_name: apple, entity_type: apple, entity_quantity: 3,
container_name: John, container_type: boy],
container2[entity_name: apple, entity_type: apple, entity_quantity: 5,
container_name: Mary, container_type: girl],
result_container[entity_name: apple, entity_type: apple, entity_quantity: 8,
container_name: John and Mary, container_type: ]
)
addition- Adding quantitiessubtraction- Subtracting quantitiesmultiplication- Repeated addition/scalingdivision- Splitting into groupscomparison- Comparing quantitiessurplus- Remainder operationsarea- Area calculationsunittrans- Unit conversions
Change mode to local in .env file:
SVG_STORAGE_MODE=local- Uses
backend/storage/datasets/svg_dataset/directory - Simple filesystem access
- Suitable for development and single-node deployment
Setup JuiceFS:
See docs/JUICEFS_SETUP.md for setup instructions.
SVG_STORAGE_MODE=juicefs
SVG_DATASET_PATH=/mnt/juicefs/svg_dataset
JUICEFS_METADATA_URL=postgres://user:pass@host:port/databaseBenefits:
- Scalability: Easy to add distributed storage later
- Backup: PostgreSQL metadata can be backed up normally
- SVG content validation and sanitization
- Removal of potentially malicious elements
- Size and complexity limits
- HTML tag stripping from Gemini API responses
- XSS prevention for tutor chat messages
- Uses nh3 library for secure HTML cleaning
- Applied before messages reach frontend
# Install ClamAV for virus scanning
sudo apt install clamav clamav-daemon
pip install pyclamdSee docs/CLAMAV_SETUP.md for configuration details.
Additional documentation:
docs/TRANSLATIONS.md- Backend translations with Flask-Babeldocs/cleanup_setup.md- Cleanup and maintenance setup
The backend includes user analytics tracking.
For detailed setup instructions, API documentation, and usage examples, see:
docs/ANALYTICS_SETUP.md
# Run test suite
python -m pytest tests/
# Test specific component
python -m pytest tests/test_svg_validator.py
# Test with coverage
python -m pytest --cov=app tests/Option 1: Manual Upload
- Create SVG file following naming conventions
- Check if name exists via
/api/svg-dataset/check-existsendpoint - Upload via
/api/svg-dataset/uploadendpoint - SVG will be validated and added to dataset
- Use
/api/svg-dataset/searchto find and manage existing entities
Option 2: AI-Powered Generation
- Check if name exists via
/api/svg-dataset/check-existsendpoint - Generate SVG via
/api/svg-dataset/generateendpoint with entity name - Review the generated SVG content
- Confirm and save via
/api/svg-dataset/confirm-generatedendpoint - Use
/api/svg-dataset/searchto find and manage existing entities
- Update operation parsers in
services/visual_generation/ - Add new operation handlers
- Update GPT prompts in
services/language_generation/
- Extend
StorageConfigclass inconfig/storage_config.py - Implement new storage validation logic
- Update environment configuration
This project is part of the Math2Visual research framework. Please refer to the main repository for licensing information.
For questions or support, please refer to the main Math2Visual repository or open an issue in this project.