commit 7a2fffd62e813f9ff06be02234771ad16e260938 Author: Jason <17367223+jsoltys@users.noreply.github.com> Date: Sat Apr 11 00:04:09 2026 -0500 Initial diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d407977 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# Flask Configuration +FLASK_APP=app.py +FLASK_ENV=development +SECRET_KEY="3pgys!#g$wA7V#%sS6GiDKEA!gwHjGR2" + +# Server Configuration +HOST=0.0.0.0 +PORT=8080 + +# Database (if needed in future) +# DATABASE_URL=sqlite:///products.db + + +# source /home/bmdwtjuw/virtualenv/product-finder/3.6/bin/activate && cd /home/bmdwtjuw/product-finder \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45793a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Flask +instance/ +.webassets-cache + +# Environment +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Logs +*.log +logs/ + +# Database +*.db +*.sqlite +*.sqlite3 + +# Testing +.pytest_cache/ +.coverage +htmlcov/ + +# Production +*.pid +*.sock diff --git a/.htaccess_fixed b/.htaccess_fixed new file mode 100644 index 0000000..10fa7bb --- /dev/null +++ b/.htaccess_fixed @@ -0,0 +1,93 @@ +# BEGIN Force HTTPS + +RewriteEngine On + +# Skip ACME challenge +RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/ + +# Redirect HTTP to HTTPS +RewriteCond %{HTTPS} off +RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + +# END Force HTTPS + +# BEGIN LSCACHE +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## + +RewriteEngine on +RewriteRule litespeed/debug/.*\.log$ - [F,L] +RewriteRule \.litespeed_conf\.dat - [F,L] + + +CacheLookup on +RewriteRule .* - [E=Cache-Control:no-autoflush] + +### marker ASYNC start ### +RewriteCond %{REQUEST_URI} /wp-admin/admin-ajax\.php +RewriteCond %{QUERY_STRING} action=async_litespeed +RewriteRule .* - [E=noabort:1] +### marker ASYNC end ### + +### marker DROPQS start ### +CacheKeyModify -qs:fbclid +CacheKeyModify -qs:gclid +CacheKeyModify -qs:utm* +CacheKeyModify -qs:_ga +### marker DROPQS end ### + + +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## +# END LSCACHE +# BEGIN NON_LSCACHE +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## +# END NON_LSCACHE + + +# BEGIN Security Block +# Block the include-only files. + +RewriteEngine On +RewriteBase / +RewriteRule ^wp-admin/includes/ - [F,L] +RewriteRule !^wp-includes/ - [S=3] +RewriteRule ^wp-includes/[^/]+\.php$ - [F,L] +RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L] +RewriteRule ^wp-includes/theme-compat/ - [F,L] + + +# Disable directory listing +Options All -Indexes + +# Remove header with PHP version +Header always unset X-Powered-By +Header unset X-Powered-By +# END Security Block + +# BEGIN LiteSpeed +# The directives (lines) between "BEGIN LiteSpeed" and "END LiteSpeed" are +# dynamically generated, and should only be modified via WordPress filters. +# Any changes to the directives between these markers will be overwritten. + +SetEnv noabort 1 + +# END LiteSpeed +# BEGIN WordPress +# The directives (lines) between "BEGIN WordPress" and "END WordPress" are +# dynamically generated, and should only be modified via WordPress filters. +# Any changes to the directives between these markers will be overwritten. + +RewriteEngine On +RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] +RewriteBase / +RewriteRule ^index\.php$ - [L] + +# *** IMPORTANT: Exclude product-finder from WordPress routing *** +RewriteCond %{REQUEST_URI} !^/product-finder/ + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . /index.php [L] + + +# END WordPress diff --git a/DYNAMIC_IMAGE_QUICKSTART.md b/DYNAMIC_IMAGE_QUICKSTART.md new file mode 100644 index 0000000..3ea46ea --- /dev/null +++ b/DYNAMIC_IMAGE_QUICKSTART.md @@ -0,0 +1,243 @@ +# Dynamic Image Generation - Quick Start + +## What Was Created + +I've built a complete server-side image generation system that can dynamically create product images with different colors and configurations. Here's what's included: + +### Files Created: +1. **`app/image_generator.py`** - Core image processing logic using PIL/Pillow +2. **`app/data/image_configs.json`** - Configuration for COBRAI product with colorable regions +3. **`app/templates/image_test.html`** - Test page to demonstrate the API +4. **`information/DYNAMIC_IMAGE_API.md`** - Complete documentation + +### Files Modified: +1. **`app/app.py`** - Added 4 new API endpoints +2. **`app/requirements.txt`** - Added Pillow dependency + +## āš ļø Important: Image File + +You need to manually save the storm door image as: +``` +app/images/cobrai.jpg +``` + +I created a placeholder file, but you need to replace it with the actual image from your screenshot (the white storm door with two screen panels). + +## Setup & Testing + +### 1. Install Dependencies +```bash +cd app +pip install Pillow +``` + +### 2. Add the Image +Save the storm door image as `app/images/cobrai.jpg` + +### 3. Start the Server +```bash +python app.py +``` + +### 4. Test the API + +#### Option A: Visit Test Page +Open browser to: +``` +http://localhost:8080/image-test +``` + +This interactive page lets you: +- Select colors (White, Black, Bronze, Sandstone) +- Choose hinge side (Left/Right) +- See both image rendering methods +- View the product configuration +- Clear the cache + +#### Option B: Direct URL Test +Visit these URLs in your browser: +``` +http://localhost:8080/api/product-image/cobrai?color=black&hinge=right +http://localhost:8080/api/product-image/cobrai?color=bronze&hinge=left +http://localhost:8080/api/product-config/cobrai +``` + +#### Option C: Using cURL +```bash +# Get black door image +curl http://localhost:8080/api/product-image/cobrai?color=black > test_black.png + +# Get bronze left-hinge door +curl http://localhost:8080/api/product-image/cobrai?color=bronze&hinge=left > test_bronze_left.png + +# Get configuration +curl http://localhost:8080/api/product-config/cobrai + +# Get as JSON with base64 +curl http://localhost:8080/api/product-image/cobrai?color=white&format=json +``` + +## How It Works + +### The Configuration +The `image_configs.json` file defines: + +1. **Colorable Regions** - Rectangular areas to recolor: + - Left frame: [0, 0] to [100, 1450] + - Right frame: [620, 0] to [720, 1450] + - Top rail: [0, 0] to [720, 80] + - Middle rail: [0, 590] to [720, 680] + - Bottom panel: [100, 1090] to [620, 1450] + +2. **Available Colors**: + - White: RGB [255, 255, 255] + - Black: RGB [30, 30, 30] + - Bronze: RGB [110, 80, 50] + - Sandstone: RGB [210, 190, 165] + +3. **Hardware Positions** - Where handles go (placeholder for future): + - Right hinge: position [580, 730] + - Left hinge: position [140, 730] (flipped) + +### The Process +1. Load white storm door image +2. For each colorable region: + - Calculate pixel brightness + - Apply target color while preserving brightness + - This maintains shadows/highlights +3. Optionally flip for left hinge +4. Cache the result for fast subsequent requests + +## API Endpoints + +### 1. Generate Image +``` +GET /api/product-image/?color=&hinge= +``` + +**Parameters:** +- `color`: white, black, bronze, sandstone +- `hinge`: left, right +- `format`: image (default) or json +- `cache`: true (default) or false + +**Returns:** PNG image or JSON with base64 + +### 2. Get Configuration +``` +GET /api/product-config/ +``` + +**Returns:** JSON with all product config (colors, regions, etc.) + +### 3. Clear Cache +``` +POST /api/clear-image-cache +``` + +**Body (optional):** +```json +{"productCode": "cobrai"} +``` + +## Adjusting the Configuration + +### If you need to change color regions: + +1. Open the image in an image editor (Paint, Photoshop, etc.) +2. Note the pixel coordinates of the area you want to color +3. Edit `app/data/image_configs.json`: + ```json + { + "name": "frame", + "topLeft": [x1, y1], + "bottomRight": [x2, y2] + } + ``` + +### If you need to adjust colors: + +Edit the RGB values in `availableColors`: +```json +"bronze": { + "rgb": [110, 80, 50], // Adjust these numbers + "name": "Bronze" +} +``` + +### If hardware images exist: + +Place hardware PNGs in `app/images/hardware/` and update config: +```json +"hardwarePositions": { + "handle_right": { + "x": 580, + "y": 730, + "image": "images/hardware/handle-lever.png" + } +} +``` + +## Next Steps + +### To integrate into your product finder: + +Replace the layered image system with dynamic generation: + +```javascript +// Instead of static layers +imageDisplayHtml = ``; + +// Or update existing image on change +function updateProductPreview(productCode) { + const color = document.getElementById('config-color').value; + const hinge = document.querySelector('[name="hinge-location"]:checked').value; + + document.getElementById('product-image').src = + `/api/product-image/${productCode}?color=${color}&hinge=${hinge}`; +} +``` + +### To add more products: + +1. Take photo of product in white/neutral color +2. Save as `app/images/productcode.jpg` +3. Add configuration to `image_configs.json` +4. Define colorable regions using image editor coordinates +5. Test at `/image-test` + +## Troubleshooting + +**"PIL/Pillow not installed"** +```bash +pip install Pillow +``` + +**"Product cobrai not found"** +- Check that image exists at `app/images/cobrai.jpg` +- Check that config exists in `image_configs.json` + +**Colors look weird** +- Adjust RGB values in config +- Ensure source image is white or neutral +- Check that region coordinates are correct + +**Image not changing** +- Try clearing cache +- Set `cache=false` in URL parameter +- Check browser console for errors + +## Performance + +- **First request**: ~100-300ms (generates and caches) +- **Cached requests**: ~10-50ms (serves from cache) +- **Cache location**: `app/cache/product_images/` + +## Full Documentation + +See `information/DYNAMIC_IMAGE_API.md` for complete documentation including: +- Advanced configuration options +- Production deployment tips +- Frontend integration examples +- Adding glass tinting, textures, etc. +- Batch generation scripts diff --git a/README.md b/README.md new file mode 100644 index 0000000..c276d05 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Product Finder Application + +A Flask-based web application for finding products through an interactive quiz interface. + +## Quick Start + +### šŸš€ Running the Application + +**Option 1: Using VS Code Tasks (Recommended)** +- Press `Ctrl+Shift+P` → Type "Tasks: Run Task" → Select "Start Flask Server" +- Open http://127.0.0.1:8080 + +**Option 2: Command Line** +```bash +cd app +python app.py +``` + +### šŸ“Š Updating Product Data + +When you update `app/data/products.csv`: +- Press `Ctrl+Shift+B` to process the data +- All JSON files will be regenerated automatically + +## Project Structure + +``` +šŸ“ app/ - All active project files (application code, assets, data) +šŸ“ information/ - Documentation and deprecated files +``` + +See [information/FOLDER_STRUCTURE.md](information/FOLDER_STRUCTURE.md) for detailed organization. + +## Documentation + +- **[FOLDER_STRUCTURE.md](information/FOLDER_STRUCTURE.md)** - Complete workspace organization +- **[QUICKSTART.md](information/QUICKSTART.md)** - Getting started guide +- **[BITWISE_USAGE_GUIDE.md](information/BITWISE_USAGE_GUIDE.md)** - Bitwise filtering documentation +- **[START_HERE.md](information/START_HERE.md)** - Project overview + +## Development + +All development happens in the `app/` folder. The project uses: +- **Flask** for the web server +- **Python** for data processing +- **Vanilla JavaScript** for the frontend + +--- + +**Version:** 2.0 +**Last Updated:** March 26, 2026 diff --git a/app.7z b/app.7z new file mode 100644 index 0000000..9ed3e8c Binary files /dev/null and b/app.7z differ diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..b8f9850 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1,7 @@ +# Apache configuration for Flask subdirectory deployment +# Passenger configuration ONLY - minimal to avoid blocking issues + +PassengerEnabled on +PassengerAppRoot /home/bmdwtjuw/product-finder +PassengerPython /home/bmdwtjuw/virtualenv/product-finder/3.13/bin/python3.13_bin +SetEnv APPLICATION_ROOT /product-finder diff --git a/app/.htaccess.minimal b/app/.htaccess.minimal new file mode 100644 index 0000000..99eb75e --- /dev/null +++ b/app/.htaccess.minimal @@ -0,0 +1,4 @@ +# MINIMAL .htaccess for troubleshooting +# If you're getting 403/404, try this simplified version first + +PassengerEnabled on diff --git a/app/admin/README.md b/app/admin/README.md new file mode 100644 index 0000000..7a614e7 --- /dev/null +++ b/app/admin/README.md @@ -0,0 +1,98 @@ +# Admin Utilities + +This folder contains administrative tools and utilities for maintaining the CGW Product Finder application. + +## šŸ› ļø Available Tools + +### fix_default_locations.py +**Purpose**: Ensures all users have their default location marked as accessible. + +**Usage**: +```bash +cd app/admin +python fix_default_locations.py +``` + +**What it does**: +- Scans all users in `data/users.json` +- Checks if each user's default location is marked as accessible +- Automatically fixes any users where the default location is not accessible +- Reports results for each user + +**When to use**: +- After manual edits to users.json +- After importing users from backup +- If users report they can't access their default location +- As a maintenance check + +### test_password_security.py +**Purpose**: Demonstrates and tests the password hashing security implementation. + +**Usage**: +```bash +cd app/admin +python test_password_security.py +``` + +**What it does**: +- Demonstrates PBKDF2-SHA256 password hashing +- Shows security features (iterations, salt, etc.) +- Verifies password verification works correctly +- Useful for understanding the security implementation + +**When to use**: +- To verify password hashing is working correctly +- To demonstrate security to stakeholders +- For educational purposes +- When troubleshooting password-related issues + +## šŸ“‹ Running Admin Tools + +All admin tools should be run from within the `app/admin` directory to ensure correct file paths. + +**Example**: +```bash +# Navigate to admin folder +cd "C:\Users\Work\Desktop\CGW Product Finder\app\admin" + +# Run a tool +python fix_default_locations.py +``` + +## āš ļø Important Notes + +- **Backup First**: Always backup `data/users.json` before running utilities that modify data +- **Test Environment**: Test utilities in a development environment before running in production +- **File Paths**: These tools assume they're being run from the `app/admin` directory +- **Python Version**: Requires Python 3.7 or higher + +## šŸ”’ Security Considerations + +- These tools have direct access to user data +- Do not expose these tools to web-facing directories +- Keep this folder secure with appropriate file permissions +- Never commit sensitive user data to version control + +## šŸ“ Adding New Admin Tools + +When adding new administrative tools to this folder: + +1. Add the Python script file +2. Update this README with documentation +3. Include clear usage instructions +4. Document any data modifications it makes +5. Include error handling and user feedback + +## šŸ› Troubleshooting + +**"File not found" errors**: +- Ensure you're running from the `app/admin` directory +- Check that `../data/users.json` exists + +**Permission denied**: +- Ensure the application is not running +- Check file permissions on `data/users.json` + +**Import errors**: +- Ensure all required dependencies are installed: `pip install -r ../requirements.txt` +- Verify Python version: `python --version` diff --git a/app/admin/fix_default_locations.py b/app/admin/fix_default_locations.py new file mode 100644 index 0000000..2b2f982 --- /dev/null +++ b/app/admin/fix_default_locations.py @@ -0,0 +1,65 @@ +""" +Utility script to fix existing users by ensuring their default location +is marked as accessible in their locationSettings. +""" + +import json +import os + +# Get the directory where this script is located +basedir = os.path.abspath(os.path.dirname(__file__)) +USERS_FILE = os.path.join(basedir, '..', 'data', 'users.json') + +def fix_default_locations(): + """ + Ensure all users have their default location marked as accessible. + """ + if not os.path.exists(USERS_FILE): + print("No users.json file found.") + return + + # Load users + with open(USERS_FILE, 'r') as f: + users = json.load(f) + + print(f"Found {len(users)} users to check...") + + changes_made = 0 + for user in users: + username = user.get('username', 'Unknown') + default_location = user.get('defaultLocation') + + if not default_location: + print(f"āš ļø User '{username}' has no default location - skipping") + continue + + location_settings = user.get('locationSettings', {}) + + # Check if default location is in settings and accessible + if default_location not in location_settings: + print(f"āœ“ Adding default location '{default_location}' for user '{username}'") + location_settings[default_location] = {'accessible': True} + user['locationSettings'] = location_settings + changes_made += 1 + elif not location_settings[default_location].get('accessible', False): + print(f"āœ“ Marking default location '{default_location}' as accessible for user '{username}'") + location_settings[default_location]['accessible'] = True + changes_made += 1 + else: + print(f" User '{username}' - default location already accessible") + + if changes_made > 0: + # Save updated users + with open(USERS_FILE, 'w') as f: + json.dump(users, f, indent=2) + print(f"\nāœ“ Fixed {changes_made} user(s) and saved to users.json") + else: + print("\nāœ“ All users already have correct default location settings") + +if __name__ == '__main__': + print("=" * 60) + print("DEFAULT LOCATION FIX UTILITY") + print("=" * 60) + print() + fix_default_locations() + print() diff --git a/app/admin/test_password_security.py b/app/admin/test_password_security.py new file mode 100644 index 0000000..b2ce388 --- /dev/null +++ b/app/admin/test_password_security.py @@ -0,0 +1,60 @@ +""" +Test script to demonstrate password hashing and verification +""" + +from werkzeug.security import generate_password_hash, check_password_hash + +# Example: Creating a hashed password +plain_password = "mySecurePassword123" +hashed_password = generate_password_hash(plain_password, method='pbkdf2:sha256') + +print("=" * 60) +print("PASSWORD HASHING DEMONSTRATION") +print("=" * 60) +print() +print(f"Original Password: {plain_password}") +print() +print(f"Hashed Password: {hashed_password}") +print() +print(f"Hash Length: {len(hashed_password)} characters") +print() +print("=" * 60) +print("SECURITY FEATURES") +print("=" * 60) +print("āœ“ Algorithm: PBKDF2-SHA256") +print("āœ“ Iterations: 600,000+") +print("āœ“ Unique salt per password") +print("āœ“ Cannot be reversed to plain text") +print("āœ“ Industry-standard security") +print() +print("=" * 60) +print("PASSWORD VERIFICATION TEST") +print("=" * 60) +print() + +# Test correct password +correct = check_password_hash(hashed_password, "mySecurePassword123") +print(f"Testing correct password: {correct} āœ“") + +# Test incorrect password +incorrect = check_password_hash(hashed_password, "wrongPassword") +print(f"Testing incorrect password: {incorrect} āœ—") +print() + +# Show that the same password produces different hashes (due to unique salts) +print("=" * 60) +print("UNIQUE SALT DEMONSTRATION") +print("=" * 60) +print() +print("Hashing the same password twice produces different hashes:") +print() +hash1 = generate_password_hash("password123", method='pbkdf2:sha256') +hash2 = generate_password_hash("password123", method='pbkdf2:sha256') +print(f"Hash 1: {hash1}") +print(f"Hash 2: {hash2}") +print() +print(f"Are they different? {hash1 != hash2}") +print("Both hashes are valid and will verify correctly!") +print(f"Hash 1 verifies: {check_password_hash(hash1, 'password123')}") +print(f"Hash 2 verifies: {check_password_hash(hash2, 'password123')}") +print() diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..57b4149 --- /dev/null +++ b/app/app.py @@ -0,0 +1,995 @@ +from flask import Flask, render_template, send_from_directory, jsonify, request, send_file, session, redirect, url_for +import os +from io import BytesIO +import base64 +import json +from werkzeug.security import generate_password_hash, check_password_hash +from functools import wraps +import secrets + +# Import image generator +try: + from image_generator import ProductImageGenerator, image_to_base64 + IMAGE_GENERATOR_AVAILABLE = True + print("āœ“ Image generation available") +except ImportError as e: + IMAGE_GENERATOR_AVAILABLE = False + print(f"Warning: Could not import image generator: {e}") + print("Image generation endpoints will be disabled.") +except Exception as e: + IMAGE_GENERATOR_AVAILABLE = False + print(f"Error loading image generator: {e}") + print("Image generation endpoints will be disabled.") + +# Get the directory where this script is located +basedir = os.path.abspath(os.path.dirname(__file__)) + +# Initialize Flask with explicit paths +app = Flask(__name__, + template_folder=os.path.join(basedir, 'templates'), + static_folder=os.path.join(basedir, 'static')) + +# Configure session - generate a random secret key +app.secret_key = secrets.token_hex(32) +app.config['SESSION_COOKIE_HTTPONLY'] = True +app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' + +# Configure static folders +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # Disable caching during development + +# Support for subdirectory deployments (e.g., /product-finder/) +# Auto-detect production environment based on Python path +import sys +if 'virtualenv/product-finder' in sys.executable or '/home/bmdwtjuw' in sys.executable: + # Running on production server + app.config['APPLICATION_ROOT'] = '/product-finder' + print("[OK] Production environment detected - using /product-finder") +else: + # Local development + app.config['APPLICATION_ROOT'] = os.environ.get('APPLICATION_ROOT', '/') + print(f"[OK] Development environment - using {app.config['APPLICATION_ROOT']}") + +# ===== SUBDIRECTORY DEPLOYMENT MIDDLEWARE ===== + +class PrefixMiddleware: + """ + Middleware to handle subdirectory deployments. + This ensures Flask knows about the /product-finder prefix when deployed. + """ + def __init__(self, app, prefix=''): + self.app = app + self.prefix = prefix.rstrip('/') # Remove trailing slash if present + + def __call__(self, environ, start_response): + # Only apply prefix if not already in SCRIPT_NAME and prefix is set + if self.prefix and self.prefix != '/': + # Check if the URL path starts with our prefix + path = environ.get('PATH_INFO', '') + script_name = environ.get('SCRIPT_NAME', '') + + # If prefix not already in SCRIPT_NAME, add it + if not script_name.startswith(self.prefix): + environ['SCRIPT_NAME'] = self.prefix + script_name + + # Remove prefix from PATH_INFO if it's there + if path.startswith(self.prefix): + environ['PATH_INFO'] = path[len(self.prefix):] + + return self.app(environ, start_response) + +# Apply middleware if APPLICATION_ROOT is set to a subdirectory +application_root = app.config.get('APPLICATION_ROOT', '/') +if application_root and application_root != '/': + app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix=application_root) + print(f"āœ“ PrefixMiddleware applied for subdirectory: {application_root}") + +# Make base URL available to all templates +@app.context_processor +def inject_base_url(): + """Inject base URL into all templates for relative paths""" + return { + 'base_url': request.script_root or '', + 'url_for': url_for + } + +# ===== AUTHENTICATION HELPERS ===== + +def login_required(f): + """Decorator to require login for routes""" + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + return redirect(url_for('login_page')) + + # Check if user is active + users = load_users() + if session['user_id'] >= len(users): + session.clear() + return redirect(url_for('login_page', message='User not found')) + + user = users[session['user_id']] + if not user.get('active', True): + session.clear() + return redirect(url_for('login_page', message='Account is inactive')) + + return f(*args, **kwargs) + return decorated_function + +def get_current_user(): + """Get the currently logged in user""" + if 'user_id' not in session: + return None + + users = load_users() + if session['user_id'] >= len(users): + return None + + return users[session['user_id']] + +def can_user(permission, location=None): + """ + Check if current user has a specific permission. + WordPress-style permission checking. + + Args: + permission (str): Permission name (e.g., 'manage_users', 'create_quotes') + location (str, optional): Check permission for specific location. + If None, uses current session location. + If 'global', checks global permissions only. + + Returns: + bool: True if user has permission, False otherwise + + Examples: + can_user('manage_users') # Check global permission + can_user('create_quotes') # Check at current location + can_user('view_reports', 'LINDS') # Check at specific location + """ + user = get_current_user() + if not user: + return False + + # Check if user is active + if not user.get('active', True): + return False + + # Check global permissions first + global_permissions = user.get('permissions', {}) + if permission in global_permissions: + return global_permissions[permission] is True + + # If location is 'global', only check global permissions + if location == 'global': + return False + + # Determine which location to check + check_location = location if location else session.get('currentLocation') + + if not check_location: + return False + + # Check location-specific permissions + location_settings = user.get('locationSettings', {}) + if check_location in location_settings: + loc_permissions = location_settings[check_location].get('permissions', {}) + if permission in loc_permissions: + return loc_permissions[permission] is True + + return False + +def user_has_any_permission(permissions, location=None): + """ + Check if user has ANY of the provided permissions. + + Args: + permissions (list): List of permission names + location (str, optional): Location to check + + Returns: + bool: True if user has at least one permission + """ + return any(can_user(perm, location) for perm in permissions) + +def user_has_all_permissions(permissions, location=None): + """ + Check if user has ALL of the provided permissions. + + Args: + permissions (list): List of permission names + location (str, optional): Location to check + + Returns: + bool: True if user has all permissions + """ + return all(can_user(perm, location) for perm in permissions) + +def permission_required(permission, location=None): + """ + Decorator to require specific permission for a route. + Similar to @login_required but checks permissions. + + Args: + permission (str): Required permission name + location (str, optional): Location to check permission for + + Example: + @app.route('/admin/users') + @login_required + @permission_required('manage_users') + def manage_users_page(): + return render_template('user_manager.html') + """ + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if not can_user(permission, location): + return render_template('access_denied.html', + required_permission=permission), 403 + return f(*args, **kwargs) + return decorated_function + return decorator + +def get_user_permissions(location=None): + """ + Get all permissions for the current user. + + Args: + location (str, optional): Get permissions for specific location. + If None, returns global + current location. + + Returns: + dict: Dictionary of permission_name: True/False + """ + user = get_current_user() + if not user: + return {} + + permissions = {} + + # Get global permissions + global_perms = user.get('permissions', {}) + permissions.update(global_perms) + + # Get location-specific permissions + if location != 'global': + check_location = location if location else session.get('currentLocation') + if check_location: + location_settings = user.get('locationSettings', {}) + if check_location in location_settings: + loc_perms = location_settings[check_location].get('permissions', {}) + permissions.update(loc_perms) + + return permissions + +# ===== USER MANAGEMENT FUNCTIONS (moved here for use in auth) ===== + +USERS_FILE = os.path.join(basedir, 'data', 'users.json') + +def load_users(): + """Load users from JSON file""" + if os.path.exists(USERS_FILE): + try: + with open(USERS_FILE, 'r') as f: + return json.load(f) + except: + return [] + return [] + +def save_users(users): + """Save users to JSON file""" + with open(USERS_FILE, 'w') as f: + json.dump(users, f, indent=2) + +# ===== AUTHENTICATION ROUTES ===== + +@app.route('/login') +def login_page(): + """Serve the login page""" + # If already logged in, redirect to main app + if 'user_id' in session: + return redirect(url_for('index')) + return render_template('login.html') + +@app.route('/select-location') +def select_location_page(): + """Serve the location selection page""" + if 'user_id' not in session: + return redirect(url_for('login_page')) + return render_template('select_location.html') + +@app.route('/api/login', methods=['POST']) +def login(): + """Authenticate user and create session""" + try: + data = request.json + username = data.get('username') + password = data.get('password') + + if not username or not password: + return jsonify({ + 'status': 'error', + 'message': 'Username and password are required' + }), 400 + + users = load_users() + + # Find user by username + user_index = None + user = None + for i, u in enumerate(users): + if u['username'] == username: + user_index = i + user = u + break + + if not user: + return jsonify({ + 'status': 'error', + 'message': 'Invalid username or password' + }), 401 + + # Check if user is active + if not user.get('active', True): + return jsonify({ + 'status': 'error', + 'message': 'Account is inactive. Please contact an administrator.' + }), 403 + + # Verify password + if not check_password_hash(user['password'], password): + return jsonify({ + 'status': 'error', + 'message': 'Invalid username or password' + }), 401 + + # Create session + session['user_id'] = user_index + session['username'] = user['username'] + session['defaultLocation'] = user.get('defaultLocation') + + # Get accessible locations + accessible_locations = [] + if 'locationSettings' in user: + for loc_code, settings in user['locationSettings'].items(): + if settings.get('accessible', False): + accessible_locations.append(loc_code) + + session['accessibleLocations'] = accessible_locations + + # Check if user needs to select a location + requiresLocationSelection = len(accessible_locations) > 1 + + # If only one location or no accessible locations, auto-select default + if not requiresLocationSelection: + session['currentLocation'] = user.get('defaultLocation') + + return jsonify({ + 'status': 'success', + 'message': 'Login successful', + 'requiresLocationSelection': requiresLocationSelection + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/api/logout', methods=['POST']) +def logout(): + """Log out user and clear session""" + session.clear() + return jsonify({ + 'status': 'success', + 'message': 'Logged out successfully' + }) + +@app.route('/api/session', methods=['GET']) +def get_session(): + """Get current session information""" + if 'user_id' not in session: + return jsonify({ + 'status': 'error', + 'message': 'Not logged in' + }), 401 + + return jsonify({ + 'status': 'success', + 'username': session.get('username'), + 'defaultLocation': session.get('defaultLocation'), + 'currentLocation': session.get('currentLocation'), + 'accessibleLocations': session.get('accessibleLocations', []) + }) + +@app.route('/api/select-location', methods=['POST']) +def select_location(): + """Select a location for the current session""" + if 'user_id' not in session: + return jsonify({ + 'status': 'error', + 'message': 'Not logged in' + }), 401 + + try: + data = request.json + location = data.get('location') + + # Verify user has access to this location + accessible = session.get('accessibleLocations', []) + if location not in accessible: + return jsonify({ + 'status': 'error', + 'message': 'You do not have access to this location' + }), 403 + + session['currentLocation'] = location + + return jsonify({ + 'status': 'success', + 'message': 'Location selected', + 'currentLocation': location + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +# ===== PERMISSION CHECK ENDPOINT ===== + +@app.route('/api/check-permission', methods=['POST']) +@login_required +def check_permission(): + """Check if current user has specific permission(s)""" + try: + data = request.json + permission = data.get('permission') + location = data.get('location') # Optional, defaults to current location + + if not permission: + return jsonify({ + 'status': 'error', + 'message': 'Permission name required' + }), 400 + + has_permission = can_user(permission, location) + + return jsonify({ + 'status': 'success', + 'hasPermission': has_permission, + 'permission': permission, + 'location': location if location else session.get('currentLocation', 'global') + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/api/user-permissions', methods=['GET']) +@login_required +def get_user_permissions_endpoint(): + """Get all permissions for current user""" + try: + location = request.args.get('location') # Optional query parameter + permissions = get_user_permissions(location) + + return jsonify({ + 'status': 'success', + 'permissions': permissions, + 'location': location if location else session.get('currentLocation', 'global') + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +# ===== MAIN APPLICATION ROUTES ===== + +@app.route('/') +def index(): + """Serve the quiz page as the main page or redirect to login""" + # Simple test to see if app is running + if 'username' not in session: + return redirect(url_for('login_page')) + + # If user hasn't selected a location yet, redirect to selection + if not session.get('currentLocation') and len(session.get('accessibleLocations', [])) > 1: + return redirect(url_for('select_location_page')) + return render_template('index2.html') + +@app.route('/test') +def test_route(): + """Simple test route to verify app is running - NO AUTH REQUIRED""" + import sys + info = { + 'status': 'OK', + 'message': 'Flask app is running!', + 'python': sys.version, + 'application_root': app.config.get('APPLICATION_ROOT'), + 'middleware': type(app.wsgi_app).__name__, + 'routes': len(list(app.url_map.iter_rules())) + } + return f""" + + App Test + +

āœ“ Flask App is Running!

+
    +
  • Python: {info['python']}
  • +
  • APPLICATION_ROOT: {info['application_root']}
  • +
  • Middleware: {info['middleware']}
  • +
  • Routes: {info['routes']}
  • +
+

Go to Login

+ + + """ + +@app.route('/quiz') +@login_required +def quiz(): + """Serve the quiz page""" + return render_template('index2.html') + +@app.route('/image-test') +@login_required +def image_test(): + """Serve the image generation test page""" + return render_template('image_test.html') + +@app.route('/css/') +def serve_css(filename): + """Serve CSS files""" + return send_from_directory(os.path.join(basedir, 'css'), filename) + +@app.route('/js/') +def serve_js(filename): + """Serve JavaScript files""" + return send_from_directory(os.path.join(basedir, 'js'), filename) + +@app.route('/images/') +def serve_images(filename): + """Serve image files""" + return send_from_directory(os.path.join(basedir, 'images'), filename) + +@app.route('/data/') +def serve_data(filename): + """Serve data files (JSON)""" + return send_from_directory(os.path.join(basedir, 'data'), filename) + +# API endpoint for future expansion (e.g., saving user selections) +@app.route('/api/save-selection', methods=['POST']) +def save_selection(): + """API endpoint to save user selections""" + data = request.json + # Here you could save to a database or file + # For now, just return success + return jsonify({ + 'status': 'success', + 'message': 'Selection saved', + 'data': data + }) + +@app.route('/api/get-products', methods=['GET']) +def get_products(): + """API endpoint to get product data""" + # This could fetch from a database in the future + return jsonify({ + 'status': 'success', + 'products': [] + }) + +# ===== DYNAMIC IMAGE GENERATION ENDPOINTS ===== + +@app.route('/api/product-image/') +def generate_product_image(product_code): + """ + Generate a product image with specified configuration. + + URL Parameters: + color: Color name (default: 'white') + hinge: Hinge side - 'left' or 'right' (default: 'right') + material: Material type (default: 'aluminum') + format: Return format - 'image' or 'json' (default: 'image') + + Examples: + /api/product-image/cobrai?color=black&hinge=left + /api/product-image/cobrai?color=bronze&hinge=right&format=json + """ + if not IMAGE_GENERATOR_AVAILABLE: + return jsonify({ + 'error': 'Image generation not available. Please install Pillow: pip install Pillow' + }), 500 + + try: + # Get parameters + color = request.args.get('color', 'white').lower() + hinge = request.args.get('hinge', 'right').lower() + material = request.args.get('material', 'aluminum').lower() + return_format = request.args.get('format', 'image').lower() + use_cache = request.args.get('cache', 'true').lower() == 'true' + + # Initialize generator + generator = ProductImageGenerator() + + # Generate image + image = generator.generate_product_image( + product_code=product_code, + color=color, + hinge=hinge, + material=material, + use_cache=use_cache + ) + + # Return based on format + if return_format == 'json': + # Return base64 encoded image in JSON + img_base64 = image_to_base64(image, format='PNG') + return jsonify({ + 'status': 'success', + 'productCode': product_code, + 'color': color, + 'hinge': hinge, + 'material': material, + 'image': img_base64, + 'format': 'base64' + }) + else: + # Return raw image file + img_io = BytesIO() + image.save(img_io, 'PNG', optimize=True) + img_io.seek(0) + return send_file(img_io, mimetype='image/png') + + except ValueError as e: + return jsonify({'error': str(e)}), 404 + except FileNotFoundError as e: + return jsonify({'error': str(e)}), 404 + except Exception as e: + return jsonify({'error': f'Error generating image: {str(e)}'}), 500 + +@app.route('/api/product-config/') +def get_product_image_config(product_code): + """ + Get the image configuration for a product. + + Returns JSON with available colors, hardware positions, and other metadata. + + Example: + /api/product-config/cobrai + """ + if not IMAGE_GENERATOR_AVAILABLE: + return jsonify({ + 'error': 'Image generation not available. Please install Pillow: pip install Pillow' + }), 500 + + try: + generator = ProductImageGenerator() + config = generator.get_product_config(product_code) + + if not config: + return jsonify({ + 'error': f'Configuration for product {product_code} not found' + }), 404 + + return jsonify({ + 'status': 'success', + 'productCode': product_code, + 'config': config + }) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/clear-image-cache', methods=['POST']) +def clear_image_cache(): + """ + Clear the image cache. + + Optional JSON body: + { "productCode": "cobrai" } - Clear cache for specific product + + Example: + POST /api/clear-image-cache + POST /api/clear-image-cache with body: {"productCode": "cobrai"} + """ + if not IMAGE_GENERATOR_AVAILABLE: + return jsonify({ + 'error': 'Image generation not available.' + }), 500 + + try: + data = request.json or {} + product_code = data.get('productCode') + + generator = ProductImageGenerator() + generator.clear_cache(product_code) + + return jsonify({ + 'status': 'success', + 'message': f'Cache cleared for {product_code}' if product_code else 'All cache cleared' + }) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + +# ===== END DYNAMIC IMAGE GENERATION ENDPOINTS ===== + +# ===== USER MANAGEMENT ENDPOINTS ===== + +@app.route('/users') +@login_required +@permission_required('manage_users') +def user_manager(): + """Serve the user management page""" + return render_template('user_manager.html') + +@app.route('/api/users', methods=['GET']) +@login_required +@permission_required('manage_users') +def get_users(): + """Get all users""" + users = load_users() + return jsonify({ + 'status': 'success', + 'users': users + }) + +@app.route('/api/users', methods=['POST']) +@login_required +@permission_required('manage_users') +def add_user(): + """Add a new user with hashed password""" + try: + data = request.json + + # Validate required fields + if not data.get('username') or not data.get('password') or not data.get('defaultLocation'): + return jsonify({ + 'status': 'error', + 'message': 'Username, password, and default location are required' + }), 400 + + users = load_users() + + # Check if username already exists + if any(user['username'] == data['username'] for user in users): + return jsonify({ + 'status': 'error', + 'message': 'Username already exists' + }), 400 + + # Hash the password using pbkdf2:sha256 (secure) + hashed_password = generate_password_hash(data['password'], method='pbkdf2:sha256') + + # Get location settings and ensure default location is marked as accessible + location_settings = data.get('locationSettings', {}) + default_location = data['defaultLocation'] + + # Ensure default location exists in settings and is marked as accessible + if default_location not in location_settings: + location_settings[default_location] = {} + location_settings[default_location]['accessible'] = True + + # Create new user + new_user = { + 'username': data['username'], + 'password': hashed_password, + 'defaultLocation': default_location, + 'locationSettings': location_settings, + 'active': data.get('active', True) # Default to active + } + + users.append(new_user) + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': 'User added successfully', + 'users': users + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/api/users//active', methods=['PATCH']) +@login_required +@permission_required('manage_users') +def toggle_user_active(user_index): + """Toggle user active status""" + try: + data = request.json + users = load_users() + + if user_index < 0 or user_index >= len(users): + return jsonify({ + 'status': 'error', + 'message': 'Invalid user index' + }), 400 + + # Update active status + users[user_index]['active'] = data.get('active', True) + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': 'User status updated', + 'users': users + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/api/users/', methods=['DELETE']) +@login_required +@permission_required('manage_users') +def delete_user(user_index): + """Delete a specific user by index""" + try: + users = load_users() + + if user_index < 0 or user_index >= len(users): + return jsonify({ + 'status': 'error', + 'message': 'Invalid user index' + }), 400 + + users.pop(user_index) + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': 'User deleted successfully', + 'users': users + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/api/users//change-password', methods=['POST']) +@login_required +@permission_required('manage_users') +def change_user_password(user_index): + """Change a user's password with current user verification""" + try: + data = request.json + new_password = data.get('newPassword') + current_user_password = data.get('currentUserPassword') + + # Validate required fields + if not new_password or not current_user_password: + return jsonify({ + 'status': 'error', + 'message': 'New password and current user password are required' + }), 400 + + # Validate password length + if len(new_password) < 6: + return jsonify({ + 'status': 'error', + 'message': 'Password must be at least 6 characters long' + }), 400 + + # Get current logged-in user + current_user = get_current_user() + if not current_user: + return jsonify({ + 'status': 'error', + 'message': 'Not authenticated' + }), 401 + + # Verify current user's password + if not check_password_hash(current_user['password'], current_user_password): + return jsonify({ + 'status': 'error', + 'message': 'Your password is incorrect. Password change denied for security reasons.' + }), 403 + + # Load users and validate index + users = load_users() + if user_index < 0 or user_index >= len(users): + return jsonify({ + 'status': 'error', + 'message': 'Invalid user index' + }), 400 + + # Hash the new password + hashed_password = generate_password_hash(new_password, method='pbkdf2:sha256') + + # Update the target user's password + target_username = users[user_index]['username'] + users[user_index]['password'] = hashed_password + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': f'Password changed successfully for user "{target_username}"' + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/api/users/clear', methods=['DELETE']) +def clear_all_users(): + """Clear all users""" + try: + save_users([]) + return jsonify({ + 'status': 'success', + 'message': 'All users cleared' + }) + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/api/users/download') +def download_users(): + """Download users as JSON file""" + try: + users = load_users() + + if not users: + return jsonify({ + 'status': 'error', + 'message': 'No users to download' + }), 400 + + # Create JSON file in memory + json_data = json.dumps(users, indent=2) + + # Send as downloadable file + return send_file( + BytesIO(json_data.encode()), + mimetype='application/json', + as_attachment=True, + download_name='users.json' + ) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +# ===== END USER MANAGEMENT ENDPOINTS ===== + +# Error handlers +@app.errorhandler(404) +def page_not_found(e): + return render_template('404.html'), 404 + +@app.errorhandler(500) +def internal_error(e): + return "Internal Server Error", 500 + +if __name__ == '__main__': + # Create necessary directories + os.makedirs(os.path.join(basedir, 'images'), exist_ok=True) + os.makedirs(os.path.join(basedir, 'templates'), exist_ok=True) + os.makedirs(os.path.join(basedir, 'css'), exist_ok=True) + os.makedirs(os.path.join(basedir, 'js'), exist_ok=True) + os.makedirs(os.path.join(basedir, 'data'), exist_ok=True) + + # Run the application + # Set debug=False for production + # Port 8080 is used instead of 5000 (Windows reserves port 5000) + app.run(debug=True, host='0.0.0.0', port=8080) diff --git a/app/cache/product_images/05385681982b54969ad4a32750bdaa8e.png b/app/cache/product_images/05385681982b54969ad4a32750bdaa8e.png new file mode 100644 index 0000000..b7735f8 Binary files /dev/null and b/app/cache/product_images/05385681982b54969ad4a32750bdaa8e.png differ diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..3a975fe --- /dev/null +++ b/app/config.py @@ -0,0 +1,44 @@ +""" +Configuration file for Flask application +""" +import os + +class Config: + """Base configuration""" + SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production' + DEBUG = False + TESTING = False + + # Application root for subdirectory deployments + # Defaults to '/' (root) for local development + # Set to '/product-finder' for production deployment + APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/') + +class DevelopmentConfig(Config): + """Development configuration""" + DEBUG = True + ENV = 'development' + # Development runs at root + APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/') + +class ProductionConfig(Config): + """Production configuration""" + DEBUG = False + ENV = 'production' + # Production deployed to /product-finder subdirectory + APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/product-finder') + # Add production-specific settings here + # DATABASE_URI = os.environ.get('DATABASE_URL') + +class TestingConfig(Config): + """Testing configuration""" + TESTING = True + DEBUG = True + +# Configuration dictionary +config = { + 'development': DevelopmentConfig, + 'production': ProductionConfig, + 'testing': TestingConfig, + 'default': DevelopmentConfig +} diff --git a/app/css/styles.css b/app/css/styles.css new file mode 100644 index 0000000..1bb3ea6 --- /dev/null +++ b/app/css/styles.css @@ -0,0 +1,737 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; + padding: 20px; +} + +.container { + max-width: 900px; + margin: 0 auto; + background-color: white; + border: 2px solid #333; + border-radius: 8px; + overflow: hidden; +} + +.header { + background-color: #333; + color: white; + padding: 20px; + text-align: center; + position: relative; +} + +.user-info-bar { + position: absolute; + top: 10px; + right: 20px; + display: flex; + align-items: center; + gap: 15px; + font-size: 14px; +} + +.logout-btn { + background-color: #f56565; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: background-color 0.3s; +} + +.logout-btn:hover { + background-color: #e53e3e; +} + +.question-container { + padding: 30px; + text-align: center; +} + +.question-title { + font-size: 24px; + font-weight: bold; + margin-bottom: 10px; + color: #333; +} + +.question-subtitle { + font-size: 16px; + color: #666; + margin-bottom: 30px; +} + +.buttons-wrapper { + max-height: 500px; + overflow-y: auto; + padding: 20px; + border-top: 1px solid #ddd; +} + +.buttons-container { + display: flex; + flex-wrap: wrap; + gap: 15px; + justify-content: center; +} + +.answer-button { + width: 180px; + padding: 15px; + background-color: white; + border: 2px solid #333; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + text-align: center; +} + +.answer-button:hover { + background-color: #f0f0f0; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.answer-button:active { + transform: translateY(0); +} + +.answer-image { + width: 100%; + height: 120px; + object-fit: cover; + border-radius: 4px; + margin-bottom: 10px; + background-color: #e0e0e0; + display: flex; + align-items: center; + justify-content: center; + font-size: 48px; + color: #999; +} + +.answer-caption { + font-size: 14px; + font-weight: bold; + color: #333; + word-wrap: break-word; +} + +.prod_code { + display: block; + font-size: 11px; + font-weight: normal; + color: #666; + margin-top: 4px; + font-style: italic; +} + +/* Product Configuration Form */ +.product-config-form { + margin-top: 20px; + padding: 20px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 8px; + text-align: left; +} + +.product-config-form h3 { + margin-bottom: 15px; + color: #333; + font-size: 18px; +} + +.config-field { + margin-bottom: 15px; +} + +.config-field label { + display: block; + margin-bottom: 5px; + color: #333; + font-weight: bold; + font-size: 14px; +} + +.config-select { + width: 100%; + padding: 8px 12px; + font-size: 14px; + border: 1px solid #ccc; + border-radius: 4px; + background-color: white; + cursor: pointer; +} + +.config-select:focus { + outline: none; + border-color: #333; +} + +.config-select:disabled { + background-color: #f0f0f0; + color: #666; + cursor: not-allowed; + opacity: 0.7; +} + +.config-input { + width: 100%; + padding: 8px 12px; + font-size: 14px; + border: 1px solid #ccc; + border-radius: 4px; + background-color: white; + box-sizing: border-box; +} + +.config-input:focus { + outline: none; + border-color: #333; +} + +.config-input::placeholder { + color: #999; +} + +.hinge-options { + display: flex; + flex-direction: column; + gap: 8px; +} + +.radio-label { + display: flex; + align-items: center; + cursor: pointer; + font-weight: normal; +} + +.radio-label input[type="radio"] { + margin-right: 8px; + cursor: pointer; +} + +.radio-label span { + font-size: 14px; + color: #333; +} + +.result-container { + padding: 40px; + text-align: center; +} + +.result-title { + font-size: 28px; + font-weight: bold; + color: #333; + margin-bottom: 30px; +} + +.result-content { + display: flex; + gap: 30px; + margin-bottom: 30px; + align-items: flex-start; + text-align: left; +} + +.result-image-section { + flex: 0 0 350px; + min-width: 350px; +} + +.product-image { + width: 100%; + height: auto; + border-radius: 8px; + border: 2px solid #333; + object-fit: contain; + max-height: 400px; + background-color: #f5f5f5; + transition: transform 0.3s ease; +} + +/* Layered Image System */ +.door-configurator { + position: relative; + width: 100%; + height: auto; + min-height: 400px; + border-radius: 8px; + border: 2px solid #333; + overflow: hidden; + background-color: #f5f5f5; +} + +.door-configurator img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: contain; + transition: transform 0.3s ease; +} + +.layer-base { + position: relative !important; + z-index: 1; +} + +.layer-door { + z-index: 2; +} + +.layer-hardware { + z-index: 3; +} + +.layer-overlay { + z-index: 4; +} + +.config-preview-notice { + position: absolute; + bottom: 10px; + left: 50%; + transform: translateX(-50%); + background-color: rgba(255, 152, 0, 0.9); + color: white; + padding: 8px 16px; + border-radius: 4px; + font-size: 12px; + font-weight: bold; + z-index: 10; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); +} + +.result-details { + flex: 1; + font-size: 16px; + color: #666; + line-height: 1.6; +} + +.result-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 20px; + gap: 10px; +} + +.actions-left, +.actions-right { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +/* Responsive layout for smaller screens */ +@media (max-width: 768px) { + .result-content { + flex-direction: column; + } + + .result-image-section { + flex: 1; + min-width: 100%; + max-width: 100%; + } +} + +.back-button { + padding: 12px 30px; + background-color: #333; + color: white; + border: none; + border-radius: 4px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.back-button:hover { + background-color: #555; +} + +/* Hide share buttons (temporarily disabled) */ +.back-button[onclick*="shareCurrentPage"] { + display: none; +} + +.breadcrumb { + padding: 15px 30px; + background-color: #f9f9f9; + border-bottom: 1px solid #ddd; + font-size: 14px; + color: #666; +} + +.breadcrumb span { + margin: 0 5px; +} + +/* Scrollbar styling */ +.buttons-wrapper::-webkit-scrollbar { + width: 10px; +} + +.buttons-wrapper::-webkit-scrollbar-track { + background: #f1f1f1; +} + +.buttons-wrapper::-webkit-scrollbar-thumb { + background: #888; + border-radius: 5px; +} + +.buttons-wrapper::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Measurement Type Toggle Buttons */ +.measurement-type-container { + display: flex; + gap: 15px; + justify-content: center; + padding: 20px; + margin: 0 auto; + max-width: 500px; +} + +.measurement-toggle { + flex: 1; + max-width: 200px; + min-height: 120px; + padding: 20px 15px; + background-color: #f0f0f0; + border: 3px solid #333; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; +} + +.measurement-toggle:hover:not(.disabled) { + background-color: #e0e0e0; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.measurement-toggle.selected { + background-color: #333; + color: white; +} + +.measurement-toggle.selected:hover { + background-color: #444; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.2); +} + +.measurement-toggle.selected .toggle-label { + color: white; +} + +.measurement-toggle.disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: #f5f5f5; +} + +.toggle-icon { + font-size: 48px; + line-height: 1; + display: block; +} + +.toggle-label { + font-size: 16px; + font-weight: bold; + color: #333; + display: block; + line-height: 1.2; +} + +.measurement-toggle.selected .toggle-icon { + filter: grayscale(0%) brightness(200%); +} + +/* Form Styles */ +.form-wrapper { + max-height: 500px; + overflow-y: auto; + padding: 20px; + border-top: 1px solid #ddd; +} + +.dynamic-form { + max-width: 500px; + margin: 0 auto; +} + +.form-group { + margin-bottom: 20px; + text-align: left; +} + +.form-group label { + display: block; + font-weight: bold; + margin-bottom: 8px; + color: #333; + font-size: 14px; +} + +.form-group input[type="text"], +.form-group input[type="number"], +.form-group input[type="email"], +.form-group input[type="tel"], +.form-group select, +.form-group textarea { + width: 100%; + padding: 10px; + border: 2px solid #333; + border-radius: 4px; + font-size: 14px; + font-family: Arial, sans-serif; + transition: border-color 0.3s ease; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: #555; +} + +.form-group textarea { + min-height: 80px; + resize: vertical; +} + +.form-group select { + cursor: pointer; + background-color: white; +} + +.submit-button { + width: 100%; + padding: 12px 30px; + background-color: #333; + color: white; + border: none; + border-radius: 4px; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s ease; + margin-top: 10px; +} + +.submit-button:hover { + background-color: #555; +} + +/* Scrollbar styling for form wrapper */ +.form-wrapper::-webkit-scrollbar { + width: 10px; +} + +.form-wrapper::-webkit-scrollbar-track { + background: #f1f1f1; +} + +.form-wrapper::-webkit-scrollbar-thumb { + background: #888; + border-radius: 5px; +} + +.form-wrapper::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Notification animations */ +@keyframes slideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Notes Section */ +.notes-section { + margin: 20px auto; + max-width: 800px; +} + +.notes-toggle { + width: 100%; + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 6px; + padding: 12px 16px; + font-size: 14px; + color: #495057; + cursor: pointer; + display: flex; + align-items: center; + gap: 10px; + transition: all 0.2s ease; + text-align: left; +} + +.notes-toggle:hover { + background-color: #e9ecef; + border-color: #ced4da; +} + +.notes-icon { + font-size: 18px; +} + +.notes-label { + flex: 1; + font-weight: 600; +} + +.notes-arrow { + font-size: 12px; + transition: transform 0.2s ease; +} + +.notes-content { + margin-top: 10px; + padding: 16px; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 6px; + font-size: 14px; + line-height: 1.6; + color: #495057; +} + +.notes-content a { + color: #007bff; + text-decoration: none; +} + +.notes-content a:hover { + text-decoration: underline; +} + +.notes-content ul { + margin: 10px 0; + padding-left: 20px; +} + +.notes-content li { + margin: 5px 0; +} + +@keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(400px); + opacity: 0; + } +} + +/* Accessories list */ +.accessories-list { + margin-top: 10px; +} + +.accessory-item { + padding: 12px; + margin: 8px 0; + background: #f5f5f5; + border-left: 3px solid #2196F3; + border-radius: 3px; + text-align: left; +} + +.accessory-item strong { + color: #333; + display: block; + margin-bottom: 5px; +} + +.accessory-item small { + color: #666; +} + +/* Product grid for results */ +.products-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin: 20px 0; + padding: 20px; +} + +.product-card { + background: white; + border: 2px solid #333; + border-radius: 8px; + padding: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + text-align: left; +} + +.product-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.2); +} + +.product-card h3 { + margin-top: 0; + color: #333; + font-size: 1.1em; + margin-bottom: 15px; +} + +.product-card p { + margin: 10px 0; + line-height: 1.6; + color: #666; +} + +.product-card ul { + list-style-position: inside; + padding-left: 0; + margin-top: 10px; +} + +.product-card li { + margin: 5px 0; + color: #666; +} diff --git a/app/data/accessories.json b/app/data/accessories.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/app/data/accessories.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/app/data/bitwise_legend.json b/app/data/bitwise_legend.json new file mode 100644 index 0000000..2ae6992 --- /dev/null +++ b/app/data/bitwise_legend.json @@ -0,0 +1,40 @@ +{ + "description": "Bitwise flag system for product classification", + "bit_definitions": { + "base_attributes": { + "bit_0 (1)": "Base Type = Door", + "bit_1 (2)": "Base Type = Window", + "bit_2 (4)": "Is Accessory", + "bit_3 (8)": "Specific Item Link" + }, + "materials": { + "bit_4 (16)": "Material = Aluminum", + "bit_5 (32)": "Material = Vinyl" + }, + "colors": { + "bit_6 (64)": "Color = Black", + "bit_7 (128)": "Color = White", + "bit_8 (256)": "Color = Bronze", + "bit_9 (512)": "Color = Tan", + "bit_10 (1024)": "Color = Mill", + "bit_11 (2048)": "Color = Sandstone" + }, + "subtypes": { + "bit_12 (4096)": "Subtype = Patio Door", + "bit_13 (8192)": "Subtype = Primary Window", + "bit_14 (16384)": "Subtype = Storm Door", + "bit_15 (32768)": "Subtype = Storm Window" + } + }, + "usage_examples": { + "check_if_door": "bit_value & 1", + "check_if_window": "bit_value & 2", + "check_if_aluminum": "bit_value & 16", + "check_if_white": "bit_value & 128", + "check_multiple": "(bit_value & 1) and (bit_value & 16) # Door AND Aluminum", + "check_subtype_patio_door": "bit_value & 4096", + "check_subtype_primary_window": "bit_value & 8192", + "check_subtype_storm_door": "bit_value & 16384", + "check_subtype_storm_window": "bit_value & 32768" + } +} \ No newline at end of file diff --git a/app/data/door_accessories_data.csv b/app/data/door_accessories_data.csv new file mode 100644 index 0000000..62c500b --- /dev/null +++ b/app/data/door_accessories_data.csv @@ -0,0 +1,15 @@ +Product_Name,Image_URL,Product_Type,Notes +Brass Lever,http://columbiawindows.com/wp-content/uploads/2014/12/CR2DLEC.jpg,Hardware, +Brass Lever (White),http://columbiawindows.com/wp-content/uploads/2014/12/CR2DLEC.jpg,Hardware,Also comes in white +Brass Lever (MT Gold),http://columbiawindows.com/wp-content/uploads/2014/12/MT-MET-GOLD.jpg,Hardware, +Brass Lever (550 Gold),http://columbiawindows.com/wp-content/uploads/2014/12/550-met-gold.jpg,Hardware, +Brass Pull,http://columbiawindows.com/wp-content/uploads/2014/12/dx-ecoat.jpg,Hardware, +Brass Pull (White),http://columbiawindows.com/wp-content/uploads/2014/12/CR3SEC.jpg,Hardware,Also comes in white +Brass Pull (MT Gold),http://columbiawindows.com/wp-content/uploads/2014/12/MT-MET-GOLD.jpg,Hardware, +Brass Pull (550 Gold),http://columbiawindows.com/wp-content/uploads/2014/12/550-met-gold.jpg,Hardware, +Satin Pull,http://columbiawindows.com/wp-content/uploads/2014/12/dx-satin-finish.jpg,Hardware, +Satin Pull (Silver),http://columbiawindows.com/wp-content/uploads/2014/12/MT-satin-silver.jpg,Hardware, +Black Pull Handle,http://columbiawindows.com/wp-content/uploads/2014/12/VPBLACK.jpg,Hardware, +White Pull Handle,http://columbiawindows.com/wp-content/uploads/2014/12/VPWHITE.jpg,Hardware, +Bevel Cut Insert,http://columbiawindows.com/wp-content/uploads/2014/12/bevel.jpg,Insert, +Brass Kick Panel,http://columbiawindows.com/wp-content/uploads/2014/12/brasskp.jpg,Panel, diff --git a/app/data/example_user_structure.json b/app/data/example_user_structure.json new file mode 100644 index 0000000..7d0f8a0 --- /dev/null +++ b/app/data/example_user_structure.json @@ -0,0 +1,68 @@ +[ + { + "username": "admin_user", + "password": "pbkdf2:sha256:1000000$examplehash$...", + "defaultLocation": "LINDS", + "active": true, + "permissions": { + "manage_users": true, + "view_reports": true, + "create_quotes": true + }, + "locationSettings": { + "LINDS": { + "accessible": true, + "permissions": { + "manage_inventory": true, + "approve_quotes": true + } + }, + "IOLA": { + "accessible": true, + "permissions": { + "manage_inventory": false, + "approve_quotes": true + } + }, + "KC": { + "accessible": false + }, + "BMD": { + "accessible": false + } + } + }, + { + "username": "standard_user", + "password": "pbkdf2:sha256:1000000$examplehash2$...", + "defaultLocation": "KC", + "active": true, + "permissions": { + "manage_users": false, + "view_reports": true, + "create_quotes": true + }, + "locationSettings": { + "LINDS": { + "accessible": true, + "permissions": { + "manage_inventory": false, + "approve_quotes": false + } + }, + "IOLA": { + "accessible": true + }, + "KC": { + "accessible": true, + "permissions": { + "manage_inventory": false, + "approve_quotes": false + } + }, + "BMD": { + "accessible": true + } + } + } +] diff --git a/app/data/image_configs.json b/app/data/image_configs.json new file mode 100644 index 0000000..ea2b310 --- /dev/null +++ b/app/data/image_configs.json @@ -0,0 +1,94 @@ +{ + "cobrai": { + "sourceImage": "images/cobrai", + "productCode": "COBRAI", + "description": "Storm Door - Full View with Screen", + "availableColors": { + "white": {"rgb": [255, 255, 255], "name": "White"}, + "black": {"rgb": [30, 30, 30], "name": "Black"}, + "bronze": {"rgb": [110, 80, 50], "name": "Bronze"}, + "sandstone": {"rgb": [210, 190, 165], "name": "Sandstone"} + }, + "colorableRegions": [ + { + "name": "region_name", + "topLeft": [24, 23], + "bottomRight": [505, 1022], + "description": "Width: 481px, Height: 999px" + }, + { + "name": "frame", + "topLeft": [0, 0], + "bottomRight": [100, 1450], + "description": "Vertical frame left side" + }, + { + "name": "frame_right", + "topLeft": [620, 0], + "bottomRight": [720, 1450], + "description": "Vertical frame right side" + }, + { + "name": "top_rail", + "topLeft": [0, 0], + "bottomRight": [720, 80], + "description": "Top horizontal rail" + }, + { + "name": "mid_rail", + "topLeft": [0, 590], + "bottomRight": [720, 680], + "description": "Middle horizontal rail" + }, + { + "name": "bottom_panel", + "topLeft": [100, 1090], + "bottomRight": [620, 1450], + "description": "Bottom kickplate panel" + } + ], + "hardwarePositions": { + "handle_right": { + "x": 580, + "y": 730, + "image": "images/hardware/handle-lever.png" + }, + "handle_left": { + "x": 140, + "y": 730, + "image": "images/hardware/handle-lever.png", + "flip": true + }, + "lock": { + "x": 360, + "y": 800, + "image": "images/hardware/lock.png" + } + }, + "glassRegions": [ + { + "name": "top_glass", + "topLeft": [100, 80], + "bottomRight": [620, 590], + "description": "Top screen/glass area" + }, + { + "name": "bottom_glass", + "topLeft": [100, 680], + "bottomRight": [620, 1090], + "description": "Bottom screen/glass area" + } + ], + "metadata": { + "imageWidth": 720, + "imageHeight": 1450, + "defaultColor": "white", + "defaultHinge": "right" + }, + "cache": { + "enabled": true, + "directory": "cache/product_images", + "maxAge": 86400 + } + } +} diff --git a/app/data/navigation.json b/app/data/navigation.json new file mode 100644 index 0000000..7697b14 --- /dev/null +++ b/app/data/navigation.json @@ -0,0 +1,461 @@ +{ + "start": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "notes": "

Quick Tips:

  • Press Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac) to hard refresh the page
  • Press F5 to reload the application
  • Use the back button or breadcrumbs to navigate
", + "answers": [ + { + "caption": "Door", + "image": "\ud83d\udeaa", + "next": "q-door-type", + "filter": { + "baseType": "Door" + } + }, + { + "caption": "Window", + "image": "\ud83e\ude9f", + "next": "q-window-type", + "filter": { + "baseType": "Window" + } + } + ] + }, + "q-door-type": { + "type": "question", + "inputType": "button", + "title": "What type of door?", + "subtitle": "Select the door category", + "answers": [ + { + "caption": "Patio Door", + "image": "\ud83d\udeaa", + "next": "q-material-patio-door", + "filter": { + "baseType": "Door", + "subType": "Patio Door" + } + }, + { + "caption": "Storm Door", + "image": "\ud83d\udeaa", + "next": "q-color-storm-door-aluminum", + "filter": { + "baseType": "Door", + "subType": "Storm Door", + "material": "Aluminum" + } + } + ] + }, + "q-material-patio-door": { + "type": "question", + "inputType": "button", + "title": "Select Material", + "subtitle": "Choose your preferred material for Patio Door", + "conditional": { + "type": "material", + "fallbackNext": "q-dimensions" + }, + "answers": [ + { + "caption": "Aluminum", + "image": "\ud83d\udd29", + "next": "q-color-patio-door-aluminum", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum" + } + }, + { + "caption": "Vinyl", + "image": "\ud83e\ude9f", + "next": "q-color-patio-door-vinyl", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Vinyl" + } + } + ] + }, + "q-color-patio-door-aluminum": { + "type": "question", + "inputType": "button", + "title": "Select Color", + "subtitle": "Choose your preferred color for Aluminum Patio Door", + "conditional": { + "type": "color", + "fallbackNext": "q-dimensions" + }, + "answers": [ + { + "caption": "Black", + "image": "\u2b1b", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum", + "color": "Black" + } + }, + { + "caption": "Bronze", + "image": "\ud83d\udfeb", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum", + "color": "Bronze" + } + }, + { + "caption": "Sandstone", + "image": "\ud83d\udfe8", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum", + "color": "Sandstone" + } + }, + { + "caption": "White", + "image": "\u2b1c", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum", + "color": "White" + } + } + ] + }, + "q-color-patio-door-vinyl": { + "type": "question", + "inputType": "button", + "title": "Select Color", + "subtitle": "Choose your preferred color for Vinyl Patio Door", + "conditional": { + "type": "color", + "fallbackNext": "q-dimensions" + }, + "answers": [ + { + "caption": "Tan", + "image": "\ud83d\udfe4", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Vinyl", + "color": "Tan" + } + }, + { + "caption": "White", + "image": "\u2b1c", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Vinyl", + "color": "White" + } + } + ] + }, + "q-color-storm-door-aluminum": { + "type": "question", + "inputType": "button", + "title": "Select Color", + "subtitle": "Choose your preferred color for Aluminum Storm Door", + "conditional": { + "type": "color", + "fallbackNext": "q-storm-door-sizes" + }, + "answers": [ + { + "caption": "Black", + "image": "\u2b1b", + "next": "q-storm-door-sizes", + "filter": { + "baseType": "Door", + "subType": "Storm Door", + "material": "Aluminum", + "color": "Black" + } + }, + { + "caption": "Bronze", + "image": "\ud83d\udfeb", + "next": "q-storm-door-sizes", + "filter": { + "baseType": "Door", + "subType": "Storm Door", + "material": "Aluminum", + "color": "Bronze" + } + }, + { + "caption": "Sandstone", + "image": "\ud83d\udfe8", + "next": "q-storm-door-sizes", + "filter": { + "baseType": "Door", + "subType": "Storm Door", + "material": "Aluminum", + "color": "Sandstone" + } + }, + { + "caption": "White", + "image": "\u2b1c", + "next": "q-storm-door-sizes", + "filter": { + "baseType": "Door", + "subType": "Storm Door", + "material": "Aluminum", + "color": "White" + } + } + ] + }, + "q-window-type": { + "type": "question", + "inputType": "button", + "title": "What type of window?", + "subtitle": "Select the window category", + "answers": [ + { + "caption": "Primary Window", + "image": "\ud83e\ude9f", + "next": "q-material-primary-window", + "filter": { + "baseType": "Window", + "subType": "Primary Window" + } + }, + { + "caption": "Storm Window", + "image": "\ud83e\ude9f", + "next": "q-color-storm-window-aluminum", + "filter": { + "baseType": "Window", + "subType": "Storm Window", + "material": "Aluminum" + } + } + ] + }, + "q-material-primary-window": { + "type": "question", + "inputType": "button", + "title": "Select Material", + "subtitle": "Choose your preferred material for Primary Window", + "conditional": { + "type": "material", + "fallbackNext": "q-dimensions" + }, + "answers": [ + { + "caption": "Aluminum", + "image": "\ud83d\udd29", + "next": "q-color-primary-window-aluminum", + "filter": { + "baseType": "Window", + "subType": "Primary Window", + "material": "Aluminum" + } + }, + { + "caption": "Vinyl", + "image": "\ud83e\ude9f", + "next": "q-dimensions", + "filter": { + "baseType": "Window", + "subType": "Primary Window", + "material": "Vinyl" + } + } + ] + }, + "q-storm-door-sizes": { + "type": "question", + "inputType": "button", + "title": "Select Storm Door Size", + "subtitle": "Choose a standard size or enter custom dimensions", + "notes": "

How to Measure:

For accurate measurements, please refer to our How to Measure Guide (PDF).

Quick Tips:

  • Measure the rough opening, not the existing unit
  • Measure width at the top, middle, and bottom - use the smallest measurement
  • Measure height on the left, center, and right - use the smallest measurement
  • Round down to the nearest inch
", + "answers": [ + { + "caption": "30\" x 81\"
2668", + "image": "šŸ“", + "next": "results", + "dimensions": { + "width": 30, + "height": 81 + } + }, + { + "caption": "32\" x 81\"
2868", + "image": "šŸ“", + "next": "results", + "dimensions": { + "width": 32, + "height": 81 + } + }, + { + "caption": "36\" x 81\"
3068", + "image": "šŸ“", + "next": "results", + "dimensions": { + "width": 36, + "height": 81 + } + }, + { + "caption": "Custom Size", + "image": "āœļø", + "next": "q-dimensions" + } + ] + }, + "q-color-primary-window-aluminum": { + "type": "question", + "inputType": "button", + "title": "Select Color", + "subtitle": "Choose your preferred color for Aluminum Primary Window", + "conditional": { + "type": "color", + "fallbackNext": "q-dimensions" + }, + "answers": [ + { + "caption": "Bronze", + "image": "\ud83d\udfeb", + "next": "q-dimensions", + "filter": { + "baseType": "Window", + "subType": "Primary Window", + "material": "Aluminum", + "color": "Bronze" + } + }, + { + "caption": "White", + "image": "\u2b1c", + "next": "q-dimensions", + "filter": { + "baseType": "Window", + "subType": "Primary Window", + "material": "Aluminum", + "color": "White" + } + } + ] + }, + "q-color-storm-window-aluminum": { + "type": "question", + "inputType": "button", + "title": "Select Color", + "subtitle": "Choose your preferred color for Aluminum Storm Window", + "conditional": { + "type": "color", + "fallbackNext": "q-dimensions" + }, + "answers": [ + { + "caption": "Black", + "image": "\u2b1b", + "next": "q-dimensions", + "filter": { + "baseType": "Window", + "subType": "Storm Window", + "material": "Aluminum", + "color": "Black" + } + }, + { + "caption": "Bronze", + "image": "\ud83d\udfeb", + "next": "q-dimensions", + "filter": { + "baseType": "Window", + "subType": "Storm Window", + "material": "Aluminum", + "color": "Bronze" + } + }, + { + "caption": "Sandstone", + "image": "\ud83d\udfe8", + "next": "q-dimensions", + "filter": { + "baseType": "Window", + "subType": "Storm Window", + "material": "Aluminum", + "color": "Sandstone" + } + }, + { + "caption": "White", + "image": "\u2b1c", + "next": "q-dimensions", + "filter": { + "baseType": "Window", + "subType": "Storm Window", + "material": "Aluminum", + "color": "White" + } + } + ] + }, + "q-dimensions": { + "type": "question", + "inputType": "form", + "title": "Select Storm Door Size", + "subtitle": "Choose a standard size or enter custom dimensions", + "notes": "

How to Measure:

For accurate measurements, please refer to our How to Measure Guide (PDF).

Quick Tips:

  • Measure the rough opening, not the existing unit
  • Measure width at the top, middle, and bottom - use the smallest measurement
  • Measure height on the left, center, and right - use the smallest measurement
  • Round down to the nearest inch
", + "measurementType": { + "defaultValue": "opening-size", + "options": [ + { + "value": "opening-size", + "label": "Opening Size", + "disabled": false + }, + { + "value": "tip-to-tip", + "label": "Tip-to-tip", + "disabled": true + } + ] + }, + "fields": [ + { + "name": "width", + "label": "Width (inches)", + "type": "number", + "required": true, + "placeholder": "26" + }, + { + "name": "height", + "label": "Height (inches)", + "type": "number", + "required": true, + "placeholder": "81" + } + ], + "next": "results" + } +} \ No newline at end of file diff --git a/app/data/product_bitwise.csv b/app/data/product_bitwise.csv new file mode 100644 index 0000000..ea70c36 --- /dev/null +++ b/app/data/product_bitwise.csv @@ -0,0 +1,922 @@ +PROD_CODE,BIT_VALUE,BIT_HEX,BIT_BINARY,DISCONTINUED,FLAGS +1650-10,16,0x10,10000,False,Aluminum +404,35282,0x89D2,1000100111010010,False,Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window +450,35282,0x89D2,1000100111010010,False,Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window +606,35282,0x89D2,1000100111010010,False,Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window +650,33234,0x81D2,1000000111010010,False,Window | Aluminum | Black | White | Bronze | Subtype:Storm Window +1400,160,0xA0,10100000,False,Vinyl | White +1500,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window +1510,160,0xA0,10100000,False,Vinyl | White +1650,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1700,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1710,16,0x10,10000,False,Aluminum +2000,8594,0x2192,10000110010010,False,Window | Aluminum | White | Bronze | Subtype:Primary Window +2100,16,0x10,10000,False,Aluminum +2200,16,0x10,10000,False,Aluminum +2650,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2700,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2710,16,0x10,10000,False,Aluminum +3000,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +3100,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +3200,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +3300,400,0x190,110010000,False,Aluminum | White | Bronze +3302,400,0x190,110010000,False,Aluminum | White | Bronze +3303,400,0x190,110010000,False,Aluminum | White | Bronze +3310,400,0x190,110010000,False,Aluminum | White | Bronze +3700,400,0x190,110010000,False,Aluminum | White | Bronze +3710,16,0x10,10000,False,Aluminum +4700,400,0x190,110010000,False,Aluminum | White | Bronze +4710,16,0x10,10000,False,Aluminum +5200,4769,0x12A1,1001010100001,False,Door | Vinyl | White | Tan | Subtype:Patio Door +5700,400,0x190,110010000,False,Aluminum | White | Bronze +6700,16,0x10,10000,False,Aluminum +265010,16,0x10,10000,False,Aluminum +1400SCR,144,0x90,10010000,False,Aluminum | White +1650SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1650VS,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1700CSC,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1700CV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1700ESC,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1700EV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1700SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +1700VS,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2000SCR,400,0x190,110010000,False,Aluminum | White | Bronze +2100EV,400,0x190,110010000,False,Aluminum | White | Bronze +2200PDG,16,0x10,10000,False,Aluminum +2650SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2650VP,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2700CV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2700EV,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2700SCR,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +2700VS,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +3000SCR,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +305INS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +306INS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +3100EV,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +3100SCR,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +310PSCR,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +3700SCR,400,0x190,110010000,False,Aluminum | White | Bronze +3700VP,400,0x190,110010000,False,Aluminum | White | Bronze +404ONE,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +450ONE,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +4700SCR,400,0x190,110010000,False,Aluminum | White | Bronze +606ONE,1488,0x5D0,10111010000,False,Aluminum | Black | White | Bronze | Mill +6100I,16785,0x4191,100000110010001,False,Door | Aluminum | White | Bronze | Subtype:Storm Door +650ONE,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +8100I,16785,0x4191,100000110010001,False,Door | Aluminum | White | Bronze | Subtype:Storm Door +BELMONT,672,0x2A0,1010100000,False,Vinyl | White | Tan +BGI,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +BGI404,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +BGI606,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +BGIST,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +C-500,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone +C1150,160,0xA0,10100000,False,Vinyl | White +C1621,16,0x10,10000,False,Aluminum +C1622,16,0x10,10000,False,Aluminum +C1626VA,16,0x10,10000,False,Aluminum +C1710,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +C1721,16,0x10,10000,False,Aluminum +C1722,16,0x10,10000,False,Aluminum +C1724,16,0x10,10000,False,Aluminum +C1800,33170,0x8192,1000000110010010,False,Window | Aluminum | White | Bronze | Subtype:Storm Window +C2021,16,0x10,10000,False,Aluminum +C2022,16,0x10,10000,False,Aluminum +C2026,16,0x10,10000,False,Aluminum +C2710,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +C300,16,0x10,10000,False,Aluminum +C3221,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +C3222,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +C3223,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +C3224,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +C3300,400,0x190,110010000,False,Aluminum | White | Bronze +C3700,16,0x10,10000,False,Aluminum +C400,160,0xA0,10100000,False,Vinyl | White +C500,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone +C500GL,0,0x0,0,False,None +C500GLP,0,0x0,0,False,None +C500PP,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone +C500SCR,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone +C500VP,2448,0x990,100110010000,False,Aluminum | White | Bronze | Sandstone +C521,400,0x190,110010000,False,Aluminum | White | Bronze +C522,400,0x190,110010000,False,Aluminum | White | Bronze +C526,400,0x190,110010000,False,Aluminum | White | Bronze +C900,400,0x190,110010000,False,Aluminum | White | Bronze +C900EV,400,0x190,110010000,False,Aluminum | White | Bronze +C900SCR,400,0x190,110010000,False,Aluminum | White | Bronze +C910,16,0x10,10000,False,Aluminum +C910PP,16,0x10,10000,False,Aluminum +C921,400,0x190,110010000,False,Aluminum | White | Bronze +C922,400,0x190,110010000,False,Aluminum | White | Bronze +C924,400,0x190,110010000,False,Aluminum | White | Bronze +C931,400,0x190,110010000,False,Aluminum | White | Bronze +C939,400,0x190,110010000,False,Aluminum | White | Bronze +C940,400,0x190,110010000,False,Aluminum | White | Bronze +C949,400,0x190,110010000,False,Aluminum | White | Bronze +C960,16,0x10,10000,False,Aluminum +COBRAI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door +COBRATI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door +CRWNFVI,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door +CRWNSDI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +D770,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window +DSGLASS,0,0x0,0,False,None +DURASEA,0,0x0,0,False,None +EXPAND,16,0x10,10000,False,Aluminum +FULLSCR,16,0x10,10000,False,Aluminum +FVGI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +FVSI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +GOLIATH,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door +HERCULE,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door +IMPERIL,4561,0x11D1,1000111010001,False,Door | Aluminum | Black | White | Bronze | Subtype:Patio Door +INSGLAS,0,0x0,0,False,None +ISP,464,0x1D0,111010000,False,Aluminum | Black | White | Bronze +IVP,4561,0x11D1,1000111010001,False,Door | Aluminum | Black | White | Bronze | Subtype:Patio Door +JET,4497,0x1191,1000110010001,False,Door | Aluminum | White | Bronze | Subtype:Patio Door +JSP,400,0x190,110010000,False,Aluminum | White | Bronze +JVP,4497,0x1191,1000110010001,False,Door | Aluminum | White | Bronze | Subtype:Patio Door +KINGDVI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door +KINGFSC,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +KINGI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door +KINGSDI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door +M1200,4497,0x1191,1000110010001,False,Door | Aluminum | White | Bronze | Subtype:Patio Door +M306,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +OUTSIDE,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +PATIOSC,6609,0x19D1,1100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Patio Door +PDSCRTT,6609,0x19D1,1100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Patio Door +PRPDS,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +PRSCR,3984,0xF90,111110010000,False,Aluminum | White | Bronze | Tan | Mill | Sandstone +PRSCR11,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +PSINS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +PWS,4048,0xFD0,111111010000,False,Aluminum | Black | White | Bronze | Tan | Mill | Sandstone +PWSINS,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +R1150,160,0xA0,10100000,False,Vinyl | White +R1400,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window +R1500,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window +R1510,32,0x20,100000,False,Vinyl +R2000,8594,0x2192,10000110010010,False,Window | Aluminum | White | Bronze | Subtype:Primary Window +R2100,400,0x190,110010000,False,Aluminum | White | Bronze +R2100EV,400,0x190,110010000,False,Aluminum | White | Bronze +R2200,400,0x190,110010000,False,Aluminum | White | Bronze +R300,16,0x10,10000,False,Aluminum +R3302,400,0x190,110010000,False,Aluminum | White | Bronze +R400,160,0xA0,10100000,False,Vinyl | White +R770,8354,0x20A2,10000010100010,False,Window | Vinyl | White | Subtype:Primary Window +R770SCR,16,0x10,10000,False,Aluminum +RCKTRAP,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +REWIRE,16,0x10,10000,False,Aluminum +RNDROCK,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +ROCKET,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +RROCKET,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +SCRI,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +SCRI404,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +SCRI606,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +SCRI808,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +SSGLASS,0,0x0,0,False,None +SSSCR,16,0x10,10000,False,Aluminum +TBGI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +TBR,4561,0x11D1,1000111010001,False,Door | Aluminum | Black | White | Bronze | Subtype:Patio Door +TGI,1424,0x590,10110010000,False,Aluminum | White | Bronze | Mill +TGI404,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +TGI606,3536,0xDD0,110111010000,False,Aluminum | Black | White | Bronze | Mill | Sandstone +TGIODD,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +THOR,16401,0x4011,100000000010001,False,Door | Aluminum | Subtype:Storm Door +TVI,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door +VKI,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +VP3700,400,0x190,110010000,False,Aluminum | White | Bronze +WINDGAT,672,0x2A0,1010100000,False,Vinyl | White | Tan +ZBARS,2512,0x9D0,100111010000,False,Aluminum | Black | White | Bronze | Sandstone +COBRATK,18897,0x49D1,100100111010001,False,Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None +,0,0x0,0,False,None diff --git a/app/data/product_bitwise.json b/app/data/product_bitwise.json new file mode 100644 index 0000000..0975e18 --- /dev/null +++ b/app/data/product_bitwise.json @@ -0,0 +1,7370 @@ +[ + { + "PROD_CODE": "1650-10", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "404", + "BIT_VALUE": 35282, + "BIT_HEX": "0x89D2", + "BIT_BINARY": "1000100111010010", + "DISCONTINUED": false, + "FLAGS": "Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window" + }, + { + "PROD_CODE": "450", + "BIT_VALUE": 35282, + "BIT_HEX": "0x89D2", + "BIT_BINARY": "1000100111010010", + "DISCONTINUED": false, + "FLAGS": "Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window" + }, + { + "PROD_CODE": "606", + "BIT_VALUE": 35282, + "BIT_HEX": "0x89D2", + "BIT_BINARY": "1000100111010010", + "DISCONTINUED": false, + "FLAGS": "Window | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Window" + }, + { + "PROD_CODE": "650", + "BIT_VALUE": 33234, + "BIT_HEX": "0x81D2", + "BIT_BINARY": "1000000111010010", + "DISCONTINUED": false, + "FLAGS": "Window | Aluminum | Black | White | Bronze | Subtype:Storm Window" + }, + { + "PROD_CODE": "1400", + "BIT_VALUE": 160, + "BIT_HEX": "0xA0", + "BIT_BINARY": "10100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White" + }, + { + "PROD_CODE": "1500", + "BIT_VALUE": 8354, + "BIT_HEX": "0x20A2", + "BIT_BINARY": "10000010100010", + "DISCONTINUED": false, + "FLAGS": "Window | Vinyl | White | Subtype:Primary Window" + }, + { + "PROD_CODE": "1510", + "BIT_VALUE": 160, + "BIT_HEX": "0xA0", + "BIT_BINARY": "10100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White" + }, + { + "PROD_CODE": "1650", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1700", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1710", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "2000", + "BIT_VALUE": 8594, + "BIT_HEX": "0x2192", + "BIT_BINARY": "10000110010010", + "DISCONTINUED": false, + "FLAGS": "Window | Aluminum | White | Bronze | Subtype:Primary Window" + }, + { + "PROD_CODE": "2100", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "2200", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "2650", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2700", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2710", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "3000", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "3100", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "3200", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "3300", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "3302", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "3303", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "3310", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "3700", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "3710", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "4700", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "4710", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "5200", + "BIT_VALUE": 4769, + "BIT_HEX": "0x12A1", + "BIT_BINARY": "1001010100001", + "DISCONTINUED": false, + "FLAGS": "Door | Vinyl | White | Tan | Subtype:Patio Door" + }, + { + "PROD_CODE": "5700", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "6700", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "265010", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "1400SCR", + "BIT_VALUE": 144, + "BIT_HEX": "0x90", + "BIT_BINARY": "10010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White" + }, + { + "PROD_CODE": "1650SCR", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1650VS", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1700CSC", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1700CV", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1700ESC", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1700EV", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1700SCR", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "1700VS", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2000SCR", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "2100EV", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "2200PDG", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "2650SCR", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2650VP", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2700CV", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2700EV", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2700SCR", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "2700VS", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "3000SCR", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "305INS", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "306INS", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "3100EV", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "3100SCR", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "310PSCR", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "3700SCR", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "3700VP", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "404ONE", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "450ONE", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "4700SCR", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "606ONE", + "BIT_VALUE": 1488, + "BIT_HEX": "0x5D0", + "BIT_BINARY": "10111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill" + }, + { + "PROD_CODE": "6100I", + "BIT_VALUE": 16785, + "BIT_HEX": "0x4191", + "BIT_BINARY": "100000110010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | White | Bronze | Subtype:Storm Door" + }, + { + "PROD_CODE": "650ONE", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "8100I", + "BIT_VALUE": 16785, + "BIT_HEX": "0x4191", + "BIT_BINARY": "100000110010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | White | Bronze | Subtype:Storm Door" + }, + { + "PROD_CODE": "BELMONT", + "BIT_VALUE": 672, + "BIT_HEX": "0x2A0", + "BIT_BINARY": "1010100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White | Tan" + }, + { + "PROD_CODE": "BGI", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "BGI404", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "BGI606", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "BGIST", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "C-500", + "BIT_VALUE": 2448, + "BIT_HEX": "0x990", + "BIT_BINARY": "100110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "C1150", + "BIT_VALUE": 160, + "BIT_HEX": "0xA0", + "BIT_BINARY": "10100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White" + }, + { + "PROD_CODE": "C1621", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C1622", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C1626VA", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C1710", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "C1721", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C1722", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C1724", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C1800", + "BIT_VALUE": 33170, + "BIT_HEX": "0x8192", + "BIT_BINARY": "1000000110010010", + "DISCONTINUED": false, + "FLAGS": "Window | Aluminum | White | Bronze | Subtype:Storm Window" + }, + { + "PROD_CODE": "C2021", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C2022", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C2026", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C2710", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "C300", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C3221", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "C3222", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "C3223", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "C3224", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "C3300", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C3700", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C400", + "BIT_VALUE": 160, + "BIT_HEX": "0xA0", + "BIT_BINARY": "10100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White" + }, + { + "PROD_CODE": "C500", + "BIT_VALUE": 2448, + "BIT_HEX": "0x990", + "BIT_BINARY": "100110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "C500GL", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "C500GLP", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "C500PP", + "BIT_VALUE": 2448, + "BIT_HEX": "0x990", + "BIT_BINARY": "100110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "C500SCR", + "BIT_VALUE": 2448, + "BIT_HEX": "0x990", + "BIT_BINARY": "100110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "C500VP", + "BIT_VALUE": 2448, + "BIT_HEX": "0x990", + "BIT_BINARY": "100110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "C521", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C522", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C526", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C900", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C900EV", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C900SCR", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C910", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C910PP", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "C921", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C922", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C924", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C931", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C939", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C940", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C949", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "C960", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "COBRAI", + "BIT_VALUE": 18897, + "BIT_HEX": "0x49D1", + "BIT_BINARY": "100100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door" + }, + { + "PROD_CODE": "COBRATI", + "BIT_VALUE": 18897, + "BIT_HEX": "0x49D1", + "BIT_BINARY": "100100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door" + }, + { + "PROD_CODE": "CRWNFVI", + "BIT_VALUE": 16401, + "BIT_HEX": "0x4011", + "BIT_BINARY": "100000000010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Subtype:Storm Door" + }, + { + "PROD_CODE": "CRWNSDI", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "D770", + "BIT_VALUE": 8354, + "BIT_HEX": "0x20A2", + "BIT_BINARY": "10000010100010", + "DISCONTINUED": false, + "FLAGS": "Window | Vinyl | White | Subtype:Primary Window" + }, + { + "PROD_CODE": "DSGLASS", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "DURASEA", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "EXPAND", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "FULLSCR", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "FVGI", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "FVSI", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "GOLIATH", + "BIT_VALUE": 16401, + "BIT_HEX": "0x4011", + "BIT_BINARY": "100000000010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Subtype:Storm Door" + }, + { + "PROD_CODE": "HERCULE", + "BIT_VALUE": 16401, + "BIT_HEX": "0x4011", + "BIT_BINARY": "100000000010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Subtype:Storm Door" + }, + { + "PROD_CODE": "IMPERIL", + "BIT_VALUE": 4561, + "BIT_HEX": "0x11D1", + "BIT_BINARY": "1000111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Subtype:Patio Door" + }, + { + "PROD_CODE": "INSGLAS", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "ISP", + "BIT_VALUE": 464, + "BIT_HEX": "0x1D0", + "BIT_BINARY": "111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze" + }, + { + "PROD_CODE": "IVP", + "BIT_VALUE": 4561, + "BIT_HEX": "0x11D1", + "BIT_BINARY": "1000111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Subtype:Patio Door" + }, + { + "PROD_CODE": "JET", + "BIT_VALUE": 4497, + "BIT_HEX": "0x1191", + "BIT_BINARY": "1000110010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | White | Bronze | Subtype:Patio Door" + }, + { + "PROD_CODE": "JSP", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "JVP", + "BIT_VALUE": 4497, + "BIT_HEX": "0x1191", + "BIT_BINARY": "1000110010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | White | Bronze | Subtype:Patio Door" + }, + { + "PROD_CODE": "KINGDVI", + "BIT_VALUE": 18897, + "BIT_HEX": "0x49D1", + "BIT_BINARY": "100100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door" + }, + { + "PROD_CODE": "KINGFSC", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "KINGI", + "BIT_VALUE": 18897, + "BIT_HEX": "0x49D1", + "BIT_BINARY": "100100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door" + }, + { + "PROD_CODE": "KINGSDI", + "BIT_VALUE": 18897, + "BIT_HEX": "0x49D1", + "BIT_BINARY": "100100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door" + }, + { + "PROD_CODE": "M1200", + "BIT_VALUE": 4497, + "BIT_HEX": "0x1191", + "BIT_BINARY": "1000110010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | White | Bronze | Subtype:Patio Door" + }, + { + "PROD_CODE": "M306", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "OUTSIDE", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "PATIOSC", + "BIT_VALUE": 6609, + "BIT_HEX": "0x19D1", + "BIT_BINARY": "1100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Patio Door" + }, + { + "PROD_CODE": "PDSCRTT", + "BIT_VALUE": 6609, + "BIT_HEX": "0x19D1", + "BIT_BINARY": "1100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Patio Door" + }, + { + "PROD_CODE": "PRPDS", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "PRSCR", + "BIT_VALUE": 3984, + "BIT_HEX": "0xF90", + "BIT_BINARY": "111110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Tan | Mill | Sandstone" + }, + { + "PROD_CODE": "PRSCR11", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "PSINS", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "PWS", + "BIT_VALUE": 4048, + "BIT_HEX": "0xFD0", + "BIT_BINARY": "111111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Tan | Mill | Sandstone" + }, + { + "PROD_CODE": "PWSINS", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "R1150", + "BIT_VALUE": 160, + "BIT_HEX": "0xA0", + "BIT_BINARY": "10100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White" + }, + { + "PROD_CODE": "R1400", + "BIT_VALUE": 8354, + "BIT_HEX": "0x20A2", + "BIT_BINARY": "10000010100010", + "DISCONTINUED": false, + "FLAGS": "Window | Vinyl | White | Subtype:Primary Window" + }, + { + "PROD_CODE": "R1500", + "BIT_VALUE": 8354, + "BIT_HEX": "0x20A2", + "BIT_BINARY": "10000010100010", + "DISCONTINUED": false, + "FLAGS": "Window | Vinyl | White | Subtype:Primary Window" + }, + { + "PROD_CODE": "R1510", + "BIT_VALUE": 32, + "BIT_HEX": "0x20", + "BIT_BINARY": "100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl" + }, + { + "PROD_CODE": "R2000", + "BIT_VALUE": 8594, + "BIT_HEX": "0x2192", + "BIT_BINARY": "10000110010010", + "DISCONTINUED": false, + "FLAGS": "Window | Aluminum | White | Bronze | Subtype:Primary Window" + }, + { + "PROD_CODE": "R2100", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "R2100EV", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "R2200", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "R300", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "R3302", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "R400", + "BIT_VALUE": 160, + "BIT_HEX": "0xA0", + "BIT_BINARY": "10100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White" + }, + { + "PROD_CODE": "R770", + "BIT_VALUE": 8354, + "BIT_HEX": "0x20A2", + "BIT_BINARY": "10000010100010", + "DISCONTINUED": false, + "FLAGS": "Window | Vinyl | White | Subtype:Primary Window" + }, + { + "PROD_CODE": "R770SCR", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "RCKTRAP", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "REWIRE", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "RNDROCK", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "ROCKET", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "RROCKET", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "SCRI", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "SCRI404", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "SCRI606", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "SCRI808", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "SSGLASS", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "SSSCR", + "BIT_VALUE": 16, + "BIT_HEX": "0x10", + "BIT_BINARY": "10000", + "DISCONTINUED": false, + "FLAGS": "Aluminum" + }, + { + "PROD_CODE": "TBGI", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "TBR", + "BIT_VALUE": 4561, + "BIT_HEX": "0x11D1", + "BIT_BINARY": "1000111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Subtype:Patio Door" + }, + { + "PROD_CODE": "TGI", + "BIT_VALUE": 1424, + "BIT_HEX": "0x590", + "BIT_BINARY": "10110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze | Mill" + }, + { + "PROD_CODE": "TGI404", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "TGI606", + "BIT_VALUE": 3536, + "BIT_HEX": "0xDD0", + "BIT_BINARY": "110111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Mill | Sandstone" + }, + { + "PROD_CODE": "TGIODD", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "THOR", + "BIT_VALUE": 16401, + "BIT_HEX": "0x4011", + "BIT_BINARY": "100000000010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Subtype:Storm Door" + }, + { + "PROD_CODE": "TVI", + "BIT_VALUE": 18897, + "BIT_HEX": "0x49D1", + "BIT_BINARY": "100100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door" + }, + { + "PROD_CODE": "VKI", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "VP3700", + "BIT_VALUE": 400, + "BIT_HEX": "0x190", + "BIT_BINARY": "110010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | White | Bronze" + }, + { + "PROD_CODE": "WINDGAT", + "BIT_VALUE": 672, + "BIT_HEX": "0x2A0", + "BIT_BINARY": "1010100000", + "DISCONTINUED": false, + "FLAGS": "Vinyl | White | Tan" + }, + { + "PROD_CODE": "ZBARS", + "BIT_VALUE": 2512, + "BIT_HEX": "0x9D0", + "BIT_BINARY": "100111010000", + "DISCONTINUED": false, + "FLAGS": "Aluminum | Black | White | Bronze | Sandstone" + }, + { + "PROD_CODE": "COBRATK", + "BIT_VALUE": 18897, + "BIT_HEX": "0x49D1", + "BIT_BINARY": "100100111010001", + "DISCONTINUED": false, + "FLAGS": "Door | Aluminum | Black | White | Bronze | Sandstone | Subtype:Storm Door" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + }, + { + "PROD_CODE": "", + "BIT_VALUE": 0, + "BIT_HEX": "0x0", + "BIT_BINARY": "0", + "DISCONTINUED": false, + "FLAGS": "None" + } +] \ No newline at end of file diff --git a/app/data/product_code_mapping.json b/app/data/product_code_mapping.json new file mode 100644 index 0000000..6cb5c98 --- /dev/null +++ b/app/data/product_code_mapping.json @@ -0,0 +1,22 @@ +{ + "COBRAI": "1100", + "COBRATI": "1100", + "TVI": "1100", + "TRTI": "1100", + "1400": "1400", + "1500": "1500", + "1650": "1650", + "2000": "2000", + "1700": "1700", + "2100": "2100", + "2700": "2700", + "3100": "3100", + "1510": "1510", + "2200": "2200", + "3200": "3200", + "3300": "3300", + "3302": "3302", + "3303": "3303", + "3310": "3310", + "2400": "2400" +} diff --git a/app/data/products-old.csv b/app/data/products-old.csv new file mode 100644 index 0000000..594dc3b --- /dev/null +++ b/app/data/products-old.csv @@ -0,0 +1,1000 @@ +,,,,,,Base,Sub-type,,Accessory,,Materials,,Colors,,,,,, +DISCONT,LOC_CODE,PROD_CODE,CATEGORY,CATEGORY_NEW,DESCRIPTION,Type,Door,Window,Yes,This Item,Aluminum,Vinyl,Black,White,Bronze,Tan,Mill,Sandstone,~ +FALSE,Iola,2100EV,2000SLI,,SERIES 2100 3-PANEL SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3000,3000DHP,,SERIES 3000 D.H. T.B REPLACEMENT WD,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3100,3000DHP,,SERIES 3100 T.B. REPLACEMENT SLIDER,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3100EV,3000DHP,,SERIES 3100 3-PANEL SLIDER,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,LINCOLN,3000DHP,,LINCOLN PRIMARY WOOD WINDOWS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,3200,3000FPP,,SERIES 3200 T.B. PICTURE WINDOW,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,BELMONT,3000FPP,,BELMONT ALLIANCE DOUBLE HUNG WINDOW,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE, +FALSE,Iola,C300,BSMT,,C-300 ALUM INSERTS FOR BASEMENT BUCKS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C400,BSMT,,C-400 VINYL INSERT FOR BASEMENT BUCKS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1800,C1800,,C-1800 INSIDE SLIDING STORM WINDOW,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3300,CASEMNT,,3300 1-PANEL T.B. CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3302,CASEMNT,,3300 2-PANEL T.B. CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3303,CASEMNT,,3300 3-PANEL T.B. CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3310,CASEMNT,,3310 FIXED CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,3400,CASEMNT,,3400 1 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3402,CASEMNT,,3400 2 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3403,CASEMNT,,3400 3 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3500,CASEMNT,,3500 1-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3502,CASEMNT,,3500 2-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3503,CASEMNT,,3503 3-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1150,CASEMNT,,C1150-VINYL AWNING WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C3300,CASEMNT,,3300 1-PANEL T.B. CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,C610,CASEMNT,,C610 VINYL CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R3302,CASEMNT,,3302 2-PANEL T.B. CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,C1500AR,CIR TOP,,C-1510 VINYL ARCH TOP-OPERATING WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C1510AR,CIR TOP,,C-1510 VINYL ARCH TOP WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C1521,CIR TOP,,C-1521 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C1526,CIR TOP,,C-1526 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1621,CIR TOP,,C-1621 INSULATED CIRCLE TOP,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1622,CIR TOP,,C-1622 INSULATED CIRCLE TOP,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1626VA,CIR TOP,,C-1626VA INSULATED CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1721,CIR TOP,,C-1721 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1722,CIR TOP,,C-1722 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1724,CIR TOP,,C-1724 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C2021,CIR TOP,,C-2021 T.B CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C2022,CIR TOP,,C-2022 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C2026,CIR TOP,,C-2026 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C3221,CIR TOP,,C-3221 T.B CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3222,CIR TOP,,C-3222 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3223,CIR TOP,,C-3223 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3224,CIR TOP,,C-3224 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C521,CIR TOP,,C-521 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C522,CIR TOP,,C-522 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C526,CIR TOP,,C-526 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,C621,CIR TOP,,C-621 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C626,CIR TOP,,C-626 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C826,CIR TOP,,C-826 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C828,CIR TOP,,C-828 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C8321,CIR TOP,,C-8321 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C8326,CIR TOP,,C-8326 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C921,CIR TOP,,C-921 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C922,CIR TOP,,C-922 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C924,CIR TOP,,C-924 T.B. CIRCLE TOPS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C931,CIR TOP,,C-931 T.B. ROUND PRIMARY WINDOW,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C939,CIR TOP,,C-939 T.B. ROUND PRIMARY WINDOW,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C940,CIR TOP,,C-940 T.B. OCTAGON PRIMARY,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C949,CIR TOP,,C949 OCTAGON THERMAL BREAK WINDOW,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,EXPAND,EXPANDR,,SILL EXPANDERS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1510,FPPW,,1510 VINYL INSULATED FIXED LITE,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1650-10,FPPW,,#1650-10 INSULATED FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1710,FPPW,,C-1710 FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2200,FPPW,,SERIES 2200 T.B. FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2200PDG,FPPW,,SERIES 2200 TB FIXED LITE W/TEMP GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,265010,FPPW,,#2650-10 SINGLE GLAZED FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2710,FPPW,,C-2710 FIXED LITE - SINGLE GLAZED,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,3710,FPPW,,C-3710 INSULATED FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,4710,FPPW,,#4710 SINGLE GLAZED FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C620,FPPW,,C620 VINYL AWNING WINDOWS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C640,FPPW,,C-640 VINYL FIXED CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C910,FPPW,,C-910 INSULATED T.B. FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C910PP,FPPW,,C-910 ARCH TOP,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C960,FPPW,,C-960 INSULATED T.B. CIRCLE TOP,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,D832,FPPW,,D832 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,DURASEA,FPPW,,"DURASEAL 5/8"" (GRAY) PER REEL",,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R1510,FPPW,,SERIES 1510 FIXED LITE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R822,FPPW,,S822 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R832,FPPW,,R832 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,S822,FPPW,,S822 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,G3000,GARDEN,,COLUMBIA COMFORT 3000 VINYL GARDEN WD,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,DSGLASS,GLASS,,DOUBLE STRENGTH GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,INSGLAS,GLASS,,INSULATED GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,SSGLASS,GLASS,,SINGLE STRENGTH GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,305INS,INSERTS,,#305 SASH WINDCHECK INSERTS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,306INS,INSERTS,,#306 SCHLEGEL GLASS INSERTS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,BGI,INSERTS,,BOTTOM GL INSERTS FOR ECONOMY ST WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,BGI404,INSERTS,,BOTTOM GL INSERTS FOR #404/450 ST WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,BGI606,INSERTS,,BOTTOM GL INSERTS FOR #606/650 ST WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,BGIST,INSERTS,,INSERTS FOR STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,FVGI,INSERTS,,GLASS INSERTS FOR KING ONE LITE,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,FVSI,INSERTS,,FULL SCREENS ONLY FOR KING ONE-LITES,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,KINGFSC,INSERTS,,FULL SCREEN FOR KING ONE-LITE,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,M306,INSERTS,,M-306 SCHLEGEL GLASS INSERTS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,PSINS,INSERTS,,PLAIN SASH INSERTS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,PWSINS,INSERTS,,INSERTS ONLY FOR PIN-ON PICTURE WINDOW,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,RCKTRAP,INSERTS,,ROCKET TRAPEZOIDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,RNDROCK,INSERTS,,ROUND ROCKET INSERT,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,ROCKET,INSERTS,,ROCKET INSERTS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,RROCKET,INSERTS,,RADIUS ROCKETS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,SCRI,INSERTS,,SCREEN INSERTS FOR ECONOMY STORM WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,SCRI404,INSERTS,,SCREEN INSERTS FOR #404/450 STORM WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,SCRI606,INSERTS,,SCREEN INSERTS FOR #606/650 STORM WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,SCRI808,INSERTS,,SCREEN INSERTS FOR #808/850 STORM WIND,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,SSSCR,INSERTS,,SCREEN INSERT FOR SELF STORING DOORS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,TBGI,INSERTS,,TEMPERED GLASS INSERTS FOR SS DOORS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,TGI,INSERTS,,TOP GL INSERTS FOR ECONOMY ST WDS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,TGI404,INSERTS,,TOP GLASS INSERTS FOR #404/450 ST WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,TGI606,INSERTS,,TOP GLASS INSERTS FOR #606/650 ST WDS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,TGIODD,INSERTS,,TOP GLASS INSERTS FOR STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,TVGROOV,INSERTS,,TEMPERED V-GROOVE ONE-LITE INSERTS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,XBUCKIN,INSERTS,,TEMPERED GLASS INSERTS FOR CROSSBUCKS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,2400,PD,,2400 ROYAL CROWN PATIO DOOR,Door,Patio Door,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,5200,PD,,5200 VINYL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE, +TRUE,Iola,C4260,PD,,C-4260 STEEL MIRROR DOOR,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,IMPERIL,PD,,IMPERIAL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,ISP,PD,,STATIONARY PANELS FOR IMPERIAL DOORS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,IVP,PD,,VENT PANELS FOR IMPERIAL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,JET,PD,,COLUMBIA JET PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,JSP,PD,,STATIONARY PANELS FOR JET DOORS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,JVP,PD,,VENT PANELS FOR JET PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,LINCPDR,PD,,LINCOLN FRENCH PATIO DOOR,Door,Patio Door,,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,M1200,PD,,M1200 PATIO STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,TBR,PD,,IMPERIAL T.B.R. REPLACEMENT PATIO DOOR,Door,Patio Door,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,PRSCR,PSCREEN,,SCREENS FOR PRIME MADE FROM #19-88,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE, +FALSE,Iola,PRSCR11,PSCREEN,,SCREEN FOR PRIME MADE FROM #19-11,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,R2100EV,R2000SL,,SERIES 2100 3-PANEL T.B. SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,R300,RBSMT,,C-300 ALUM INSERTS FOR BASEMENT BUCKS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R400,RBSMT,,C-400 VINYL INSERT FOR BASEMENT BUCKS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,4100,REPLACE,,SERIES 4100 VINYL SLIDER,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C4000,REPLACE,,C-4000 SINGLE HUNGE PRIMARY WINDOW,Window,,Primary Window,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R1150,REPLACE,,R-1150 VINYL AWNING WINDOWS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R1400,REPLACE,,SERIES 1400 VINYL SLIDING PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R1500,REPLACE,,SERIES 1500 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R2000,REPLACE,,SERIES 2000 S.H. T.B. TILT PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,R2100,REPLACE,,SERIES 2100 THERMAL BREAK SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,R5000,REPLACE,,R-5000 INSULATED ALUMINUM SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R770,REPLACE,,R770 D.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R780,REPLACE,,R780 VINYL SLIDING PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R820,REPLACE,,R820 S.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R821,REPLACE,,S821 SINGLE SLIDE VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R830,REPLACE,,R830 D.H.TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R2200,RFPPW,,SERIES 2200 T.B. FIXED LITE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,1650SCR,SCREENS,,SCREENS FOR #1650,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700CSC,SCREENS,,SCREEN FOR #1700 CENTER VENT SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700ESC,SCREENS,,SCREEN FOR #1700 ENDS VENT SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700SCR,SCREENS,,SCREEN FOR #1700 SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2650SCR,SCREENS,,SCREENS FOR 2650,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700SCR,SCREENS,,SCREENS FOR #2700,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,3000SCR,SCREENS,,SCREENS FOR #3000,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3100SCR,SCREENS,,SCREENS FOR #3100,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3700SCR,SCREENS,,SCREEN FOR #3700 POLE BARN WD,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,4700SCR,SCREENS,,SCREENS FOR #4700,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,5000SCR,SCREENS,,SCREEN FOR R5000 SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C500SCR,SCREENS,,C-500 SCREEN,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C900SCR,SCREENS,,SCREEN FOR C-900,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,FULLSCR,SCREENS,,FULL SCREEN,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,PATIOSC,SCREENS,,PATIO DOOR SCREENS,Door,Patio Door,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,PDSCRTT,SCREENS,,SPECIAL SIZE SCREENS FOR PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,PRPDS,SCREENS,,SCREENS MADE FROM PLAIN PATIO SCR RAIL,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,R770SCR,SCREENS,,FULL SCREEN,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,REWIRE,SCREENS,,REWIRED SCREENS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,310PSCR,SCRINS,,#310 PLAIN SCREENS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,1500,SHPT,,SERIES 1500 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2000,SHPT,,SERIES 2000 S.H. T.B. TILT PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,2000SCR,SHPT,,C-2000 SCREEN,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,2100,SHPT,,SERIES 2100 THERMAL BREAK SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,6300,SHPT,,SERIES 6300 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,D770,SHPT,,D770 D.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,D830,SHPT,,D830 D.H.TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,D830SCR,SHPT,,D830 SCREEN,Window,,Primary Window,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,S820,SHPT,,S820 S.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1400,SHPW,,SERIES 1400 VINYL SLIDING PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1400SCR,SHPW,,1400 SCREEN,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1650,SHPW,,#1650 INSULATED SINGLE HUNG,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1650VS,SHPW,,1650 BOTTOM SASH,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700,SHPW,,#1700 INSULATED SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700CV,SHPW,,1700 CENTER VENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700EV,SHPW,,1700 END VENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700VS,SHPW,,#1700 VENT SASH,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2650,SHPW,,#2650 SINGLE HUNG SINGLE GLAZED PRIME,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2650VP,SHPW,,#2650 VENT PANEL - SINGLE HUNGE,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700,SHPW,,#2700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700CV,SHPW,,C-2700 - CENTER VENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700EV,SHPW,,C-2700 - END VENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700VS,SHPW,,#2700 VENT SASH,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,3700,SHPW,,#3700 INSULATED SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3700VP,SHPW,,#3700 VENT PANEL,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,4700,SHPW,,#4700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,5700,SHPW,,#5700 INSULATED SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,6301,SHPW,,SERIES 6301 VINYL SLIDING PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,6700,SHPW,,#6700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C-500,SHPW,,C-500 SH THERMAL BREAK INS.,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C1710,SHPW,,#1710 PICTURE OVER SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,C2710,SHPW,,#2710 PICTURE OVER SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,C3700,SHPW,,C-3700 INSULATED SLIDING POLE BARN WND,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C500,SHPW,,C-500 INS THERMAL BREAK SINGLE HUNG,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C500PP,SHPW,,C-500 INS THERMAL BREAK SINGLE HUNG,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C900,SHPW,,C-900 INS THERMAL BREAK SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C900EV,SHPW,,C-900 ENDS VENT SLIDER,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,D780,SHPW,,D780 DOUBLE SLIDE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,S821,SHPW,,S821 SINGLE SLIDE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,6100I,STD,,STAR 6100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,6100L,STD,,STAR 6100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,7100I,STD,,STAR 7100 FULL VIEW SELF STORING DOOR,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,7100L,STD,,STAR 7100 FULL VIEW SELF STORING DOOR,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,8100I,STD,,STAR 8100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,8100L,STD,,STAR 8100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,COBRAI,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,COBRAL,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,COBRATI,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,COBRATL,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,CRWNFVI,STD,,CROWN FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,CRWNFVL,STD,,CROWN FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,CRWNSDI,STD,,COLUMBIA CROWN SCREEN DOOR,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,CRWNSDL,STD,,COLUMBIA CROWN SCREEN DOOR,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV10I,STD,,KING FV-10 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV10L,STD,,KING FV-10 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV3I,STD,,KING FV-3 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV3L,STD,,KING FV-3 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,GOLIATH,STD,,GOLIATH STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,HERCULE,STD,,HERCULES STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,KINGDVI,STD,,KING DUAL VENT STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,KINGDVL,STD,,KING DUAL VENT STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,KINGI,STD,,KING ONE-LITE STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,KINGL,STD,,KING ONE-LITE STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,KINGSDI,STD,,KING ONE-LITE SCREEN DOOR ONLY,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,KINGSDL,STD,,KING ONE-LITE SCREEN DOOR ONLY,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,ROYAL,STD,,COLUMBIA ROYAL STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,SS10I,STD,,COBRA SS-10 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,SS10L,STD,,COBRA SS-10 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,SS3I,STD,,COBRA SS-3 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,SS3L,STD,,COBRA SS-3 DECORATOR STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,THOR,STD,,THOR - STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,TIARAI,STD,,COLUMBIA TIARA SELF STORING STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,TIARAL,STD,,COLUMBIA TIARA SELF STORING STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,TVI,STD,,COLUMBIA COBRA TWIN VENT STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,TVL,STD,,COLUMBIA COBRA TWIN VENT STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,OUTSIDE,STDMISC,,OUTSIDE DOOR SWEEPS - ALUM + VINYL,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,ZBARS,STDMISC,,Z-BARS FOR STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,VKI,STOCK,,VENTILATOR KICKPANEL FOR KING ONELITE,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,VKS,STOCK,,VENTILATOR SCREEN FOR KING ONE LITE,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,303,STORMS,,#303 DART STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,350,STORMS,,#350 ARROW STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,404,STORMS,,#404 FALCON STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,450,STORMS,,#450 RAVEN STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,606,STORMS,,#606 LION STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,650,STORMS,,#650 LYNX STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,808,STORMS,,808 HAWK STORM WINDOW,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,850,STORMS,,#850 KENT STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,404ONE,STPW,,#404 ONE-LITE ST WD,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,450ONE,STPW,,#450 ONE-LITE ST WD,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,606ONE,STPW,,#606 ONE-LITE ST WD,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,650ONE,STPW,,#650 ONE-LITE ST WD,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +TRUE,Iola,808ONE,STPW,,#808 ONE-LITE ST WD,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,850ONE,STPW,,#850 ONE-LITE ST WD,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,PWS,STPW,,PIN-ON PICTURE WINDOWS,,,,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE, +FALSE,Iola,WINDGAT,VACW,,ALLIANCE WINDGATE CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE, +FALSE,Iola,C500GL,VENTS,,LITES OF INSULATED GLASS FOR C-500'S,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C500GLP,VENTS,,LITES OF INSULATED GLASS FOR C-500'S,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C500VP,VENTS,,VENT PANELS FOR C-500,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,VP3700,VENTS,,VENT PANELS FOR #3700,,,,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,KC,,,,Cobra aluminum storm door,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,KC,,,,Cobra Twin-Vent aluminum storm door,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,KC,,,,Cobra aluminum storm door with Glass Kick Panel,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, \ No newline at end of file diff --git a/app/data/products.csv b/app/data/products.csv new file mode 100644 index 0000000..002b3ff --- /dev/null +++ b/app/data/products.csv @@ -0,0 +1,998 @@ +,,,,,,Base,Sub-type,,Accessory,,Materials,,Colors,,,,,, +DISCONT,LOC_CODE,PROD_CODE,CATEGORY,CATEGORY_NEW,DESCRIPTION,Type,Door,Window,Yes,This Item,Aluminum,Vinyl,Black,White,Bronze,Tan,Mill,Sandstone,~ +FALSE,Iola,1650-10,FPPW,,#1650-10 INSULATED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,303,STORMS,,#303 DART STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,350,STORMS,,#350 ARROW STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,404,STORMS,,#404 FALCON STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,450,STORMS,,#450 RAVEN STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,606,STORMS,,#606 LION STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,650,STORMS,,#650 LYNX STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,808,STORMS,,808 HAWK STORM WINDOW,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,850,STORMS,,#850 KENT STORM WINDOWS,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1400,SHPW,,SERIES 1400 VINYL SLIDING PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1500,SHPT,,SERIES 1500 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1510,FPPW,,1510 VINYL INSULATED FIXED LITE,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1650,SHPW,,#1650 INSULATED SINGLE HUNG,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700,SHPW,,#1700 INSULATED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1710,FPPW,,C-1710 FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2000,SHPT,,SERIES 2000 S.H. T.B. TILT PRIMARY,Window,,Primary Window,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,2100,SHPT,,SERIES 2100 THERMAL BREAK SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2200,FPPW,,SERIES 2200 T.B. FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,2400,PD,,2400 ROYAL CROWN PATIO DOOR,Door,Patio Door,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2650,SHPW,,#2650 SINGLE HUNG SINGLE GLAZED PRIME,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700,SHPW,,#2700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2710,FPPW,,C-2710 FIXED LITE - SINGLE GLAZED,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,3000,3000DHP,#N/A,SERIES 3000 D.H. T.B REPLACEMENT WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3100,3000DHP,#N/A,SERIES 3100 T.B. REPLACEMENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3200,3000FPP,#N/A,SERIES 3200 T.B. PICTURE WINDOW,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3300,CASEMNT,,3300 1-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3302,CASEMNT,,3300 2-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3303,CASEMNT,,3300 3-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3310,CASEMNT,,3310 FIXED CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,3400,CASEMNT,,3400 1 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3402,CASEMNT,,3400 2 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3403,CASEMNT,,3400 3 PANEL VINYL CLAD CASEMENT,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3500,CASEMNT,,3500 1-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3502,CASEMNT,,3500 2-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,3503,CASEMNT,,3503 3-PANEL WOOD INTERIOR CASEMENT,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,3700,SHPW,,#3700 INSULATED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3710,FPPW,,C-3710 INSULATED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,4100,REPLACE,,SERIES 4100 VINYL SLIDER,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,4700,SHPW,,#4700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,4710,FPPW,,#4710 SINGLE GLAZED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,5200,PD,,5200 VINYL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE, +FALSE,Iola,5700,SHPW,,#5700 INSULATED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,6300,SHPT,,SERIES 6300 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,6301,SHPW,,SERIES 6301 VINYL SLIDING PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,6700,SHPW,,#6700 SINGLE GLAZED SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,265010,FPPW,,#2650-10 SINGLE GLAZED FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1400SCR,SHPW,,1400 SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,1650SCR,SCREENS,,SCREENS FOR #1650,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1650VS,SHPW,,1650 BOTTOM SASH,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700CSC,SCREENS,,SCREEN FOR #1700 CENTER VENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700CV,SHPW,,1700 CENTER VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700ESC,SCREENS,,SCREEN FOR #1700 ENDS VENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700EV,SHPW,,1700 END VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700SCR,SCREENS,,SCREEN FOR #1700 SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,1700VS,SHPW,,#1700 VENT SASH,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2000SCR,SHPT,,C-2000 SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,2100EV,2000SLI,#N/A,SERIES 2100 3-PANEL SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,2200PDG,FPPW,,SERIES 2200 TB FIXED LITE W/TEMP GLASS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,2650SCR,SCREENS,,SCREENS FOR 2650,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2650VP,SHPW,,#2650 VENT PANEL - SINGLE HUNGE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700CV,SHPW,,C-2700 - CENTER VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700EV,SHPW,,C-2700 - END VENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700SCR,SCREENS,,SCREENS FOR #2700,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,2700VS,SHPW,,#2700 VENT SASH,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,3000SCR,SCREENS,,SCREENS FOR #3000,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,305INS,INSERTS,,#305 SASH WINDCHECK INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,306INS,INSERTS,,#306 SCHLEGEL GLASS INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,3100EV,3000DHP,#N/A,SERIES 3100 3-PANEL SLIDER,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3100SCR,SCREENS,,SCREENS FOR #3100,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,310PSCR,SCRINS,,#310 PLAIN SCREENS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,3700SCR,SCREENS,,SCREEN FOR #3700 POLE BARN WD,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,3700VP,SHPW,,#3700 VENT PANEL,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,404ONE,STPW,,#404 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,450ONE,STPW,,#450 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,4700SCR,SCREENS,,SCREENS FOR #4700,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,5000SCR,SCREENS,,SCREEN FOR R5000 SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,606ONE,STPW,,#606 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,6100I,STD,,STAR 6100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,6100L,STD,,STAR 6100 FULL VIEW STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,650ONE,STPW,,#650 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +TRUE,Iola,7100I,STD,,STAR 7100 FULL VIEW SELF STORING DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,7100L,STD,,STAR 7100 FULL VIEW SELF STORING DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,808ONE,STPW,,#808 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,8100I,STD,,STAR 8100 FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,8100L,STD,,STAR 8100 FULL VIEW STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,850ONE,STPW,,#850 ONE-LITE ST WD,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,BELMONT,3000FPP,#N/A,BELMONT ALLIANCE DOUBLE HUNG WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE, +FALSE,Iola,BGI,INSERTS,,BOTTOM GL INSERTS FOR ECONOMY ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,BGI404,INSERTS,,BOTTOM GL INSERTS FOR #404/450 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,BGI606,INSERTS,,BOTTOM GL INSERTS FOR #606/650 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,BGIST,INSERTS,,INSERTS FOR STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,C-500,SHPW,,C-500 SH THERMAL BREAK INS.,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C1150,CASEMNT,,C1150-VINYL AWNING WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C1500AR,CIR TOP,,C-1510 VINYL ARCH TOP-OPERATING WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C1510AR,CIR TOP,,C-1510 VINYL ARCH TOP WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C1521,CIR TOP,,C-1521 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C1526,CIR TOP,,C-1526 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1621,CIR TOP,,C-1621 INSULATED CIRCLE TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1622,CIR TOP,,C-1622 INSULATED CIRCLE TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1626VA,CIR TOP,,C-1626VA INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1710,SHPW,,#1710 PICTURE OVER SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,C1721,CIR TOP,,C-1721 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1722,CIR TOP,,C-1722 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1724,CIR TOP,,C-1724 INSULATED CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C1800,C1800,#N/A,C-1800 INSIDE SLIDING STORM WINDOW,Window,,Storm Window,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C2021,CIR TOP,,C-2021 T.B CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C2022,CIR TOP,,C-2022 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C2026,CIR TOP,,C-2026 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C2710,SHPW,,#2710 PICTURE OVER SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,C300,BSMT,#N/A,C-300 ALUM INSERTS FOR BASEMENT BUCKS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C3221,CIR TOP,,C-3221 T.B CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3222,CIR TOP,,C-3222 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3223,CIR TOP,,C-3223 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3224,CIR TOP,,C-3224 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3300,CASEMNT,,3300 1-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C3700,SHPW,,C-3700 INSULATED SLIDING POLE BARN WND,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C400,BSMT,#N/A,C-400 VINYL INSERT FOR BASEMENT BUCKS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C4000,REPLACE,,C-4000 SINGLE HUNGE PRIMARY WINDOW,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C4260,PD,,C-4260 STEEL MIRROR DOOR,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C500,SHPW,,C-500 INS THERMAL BREAK SINGLE HUNG,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C500GL,VENTS,,LITES OF INSULATED GLASS FOR C-500'S,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C500GLP,VENTS,,LITES OF INSULATED GLASS FOR C-500'S,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C500PP,SHPW,,C-500 INS THERMAL BREAK SINGLE HUNG,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C500SCR,SCREENS,,C-500 SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C500VP,VENTS,,VENT PANELS FOR C-500,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,C521,CIR TOP,,C-521 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C522,CIR TOP,,C-522 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C526,CIR TOP,,C-526 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +TRUE,Iola,C610,CASEMNT,,C610 VINYL CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C620,FPPW,,C620 VINYL AWNING WINDOWS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C621,CIR TOP,,C-621 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C626,CIR TOP,,C-626 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C640,FPPW,,C-640 VINYL FIXED CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C826,CIR TOP,,C-826 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C828,CIR TOP,,C-828 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C8321,CIR TOP,,C-8321 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,C8326,CIR TOP,,C-8326 VINYL CIRCLE TOP,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C900,SHPW,,C-900 INS THERMAL BREAK SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C900EV,SHPW,,C-900 ENDS VENT SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C900SCR,SCREENS,,SCREEN FOR C-900,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C910,FPPW,,C-910 INSULATED T.B. FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C910PP,FPPW,,C-910 ARCH TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,C921,CIR TOP,,C-921 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C922,CIR TOP,,C-922 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C924,CIR TOP,,C-924 T.B. CIRCLE TOPS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C931,CIR TOP,,C-931 T.B. ROUND PRIMARY WINDOW,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C939,CIR TOP,,C-939 T.B. ROUND PRIMARY WINDOW,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C940,CIR TOP,,C-940 T.B. OCTAGON PRIMARY,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C949,CIR TOP,,C949 OCTAGON THERMAL BREAK WINDOW,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,C960,FPPW,,C-960 INSULATED T.B. CIRCLE TOP,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,COBRAI,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,COBRAL,STD,,COLUMBIA COBRA STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,COBRATI,STD,,COLUMBIA COBRA STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,COBRATL,STD,,COLUMBIA COBRA STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,CRWNFVI,STD,,CROWN FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,CRWNFVL,STD,,CROWN FULL VIEW STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,CRWNSDI,STD,,COLUMBIA CROWN SCREEN DOOR,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,CRWNSDL,STD,,COLUMBIA CROWN SCREEN DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,D770,SHPT,,D770 D.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,D780,SHPW,,D780 DOUBLE SLIDE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,D830,SHPT,,D830 D.H.TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,D830SCR,SHPT,,D830 SCREEN,Window,,Primary Window,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,D832,FPPW,,D832 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,DSGLASS,GLASS,,DOUBLE STRENGTH GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,DURASEA,FPPW,,"DURASEAL 5/8"" (GRAY) PER REEL",,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,EXPAND,EXPANDR,,SILL EXPANDERS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,FULLSCR,SCREENS,,FULL SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV10I,STD,,KING FV-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV10L,STD,,KING FV-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV3I,STD,,KING FV-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,FV3L,STD,,KING FV-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,FVGI,INSERTS,,GLASS INSERTS FOR KING ONE LITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,FVSI,INSERTS,,FULL SCREENS ONLY FOR KING ONE-LITES,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,G3000,GARDEN,,COLUMBIA COMFORT 3000 VINYL GARDEN WD,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,GOLIATH,STD,,GOLIATH STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,HERCULE,STD,,HERCULES STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,IMPERIL,PD,,IMPERIAL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,INSGLAS,GLASS,,INSULATED GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,ISP,PD,,STATIONARY PANELS FOR IMPERIAL DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,IVP,PD,,VENT PANELS FOR IMPERIAL PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,JET,PD,,COLUMBIA JET PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,JSP,PD,,STATIONARY PANELS FOR JET DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,JVP,PD,,VENT PANELS FOR JET PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,KINGDVI,STD,,KING DUAL VENT STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,KINGDVL,STD,,KING DUAL VENT STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,KINGFSC,INSERTS,,FULL SCREEN FOR KING ONE-LITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,KINGI,STD,,KING ONE-LITE STORM DOORS,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,KINGL,STD,,KING ONE-LITE STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,KINGSDI,STD,,KING ONE-LITE SCREEN DOOR ONLY,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,KINGSDL,STD,,KING ONE-LITE SCREEN DOOR ONLY,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,LINCOLN,3000DHP,#N/A,LINCOLN PRIMARY WOOD WINDOWS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,LINCPDR,PD,,LINCOLN FRENCH PATIO DOOR,Door,Patio Door,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,M1200,PD,,M1200 PATIO STORM DOOR,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,M306,INSERTS,,M-306 SCHLEGEL GLASS INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,OUTSIDE,STDMISC,,OUTSIDE DOOR SWEEPS - ALUM + VINYL,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,PATIOSC,SCREENS,,PATIO DOOR SCREENS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,PDSCRTT,SCREENS,,SPECIAL SIZE SCREENS FOR PATIO DOORS,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,PRPDS,SCREENS,,SCREENS MADE FROM PLAIN PATIO SCR RAIL,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,PRSCR,PSCREEN,,SCREENS FOR PRIME MADE FROM #19-88,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE, +FALSE,Iola,PRSCR11,PSCREEN,,SCREEN FOR PRIME MADE FROM #19-11,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,PSINS,INSERTS,,PLAIN SASH INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,PWS,STPW,,PIN-ON PICTURE WINDOWS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE, +FALSE,Iola,PWSINS,INSERTS,,INSERTS ONLY FOR PIN-ON PICTURE WINDOW,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,R1150,REPLACE,,R-1150 VINYL AWNING WINDOWS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R1400,REPLACE,,SERIES 1400 VINYL SLIDING PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R1500,REPLACE,,SERIES 1500 S.H. TILT VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R1510,FPPW,,SERIES 1510 FIXED LITE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R2000,REPLACE,,SERIES 2000 S.H. T.B. TILT PRIMARY,Window,,Primary Window,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,R2100,REPLACE,,SERIES 2100 THERMAL BREAK SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,R2100EV,R2000SL,,SERIES 2100 3-PANEL T.B. SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,R2200,RFPPW,,SERIES 2200 T.B. FIXED LITE,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,R300,RBSMT,,C-300 ALUM INSERTS FOR BASEMENT BUCKS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R3302,CASEMNT,,3302 2-PANEL T.B. CASEMENT,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,R400,RBSMT,,C-400 VINYL INSERT FOR BASEMENT BUCKS,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R5000,REPLACE,,R-5000 INSULATED ALUMINUM SLIDER,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R770,REPLACE,,R770 D.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,R770SCR,SCREENS,,FULL SCREEN,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R780,REPLACE,,R780 VINYL SLIDING PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R820,REPLACE,,R820 S.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R821,REPLACE,,S821 SINGLE SLIDE VINYL PRIMARY,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R822,FPPW,,S822 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R830,REPLACE,,R830 D.H.TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,R832,FPPW,,R832 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,RCKTRAP,INSERTS,,ROCKET TRAPEZOIDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,REWIRE,SCREENS,,REWIRED SCREENS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,RNDROCK,INSERTS,,ROUND ROCKET INSERT,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,ROCKET,INSERTS,,ROCKET INSERTS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +TRUE,Iola,ROYAL,STD,,COLUMBIA ROYAL STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,RROCKET,INSERTS,,RADIUS ROCKETS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +TRUE,Iola,S820,SHPT,,S820 S.H. TILT VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,S821,SHPW,,S821 SINGLE SLIDE VINYL PRIMARY,,,,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,S822,FPPW,,S822 FIXED VINYL PRIMARY WINDOWS,Window,,Primary Window,FALSE,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,SCRI,INSERTS,,SCREEN INSERTS FOR ECONOMY STORM WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,SCRI404,INSERTS,,SCREEN INSERTS FOR #404/450 STORM WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,SCRI606,INSERTS,,SCREEN INSERTS FOR #606/650 STORM WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,SCRI808,INSERTS,,SCREEN INSERTS FOR #808/850 STORM WIND,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +TRUE,Iola,SS10I,STD,,COBRA SS-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,SS10L,STD,,COBRA SS-10 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,SS3I,STD,,COBRA SS-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,SS3L,STD,,COBRA SS-3 DECORATOR STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,SSGLASS,GLASS,,SINGLE STRENGTH GLASS,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,SSSCR,INSERTS,,SCREEN INSERT FOR SELF STORING DOORS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,TBGI,INSERTS,,TEMPERED GLASS INSERTS FOR SS DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,TBR,PD,,IMPERIAL T.B.R. REPLACEMENT PATIO DOOR,Door,Patio Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,TGI,INSERTS,,TOP GL INSERTS FOR ECONOMY ST WDS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,FALSE, +FALSE,Iola,TGI404,INSERTS,,TOP GLASS INSERTS FOR #404/450 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,TGI606,INSERTS,,TOP GLASS INSERTS FOR #606/650 ST WDS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,TRUE,TRUE, +FALSE,Iola,TGIODD,INSERTS,,TOP GLASS INSERTS FOR STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,THOR,STD,,THOR - STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,TIARAI,STD,,COLUMBIA TIARA SELF STORING STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,TIARAL,STD,,COLUMBIA TIARA SELF STORING STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +TRUE,Iola,TVGROOV,INSERTS,,TEMPERED V-GROOVE ONE-LITE INSERTS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,TVI,STD,,COLUMBIA COBRA TWIN VENT STORM DOOR,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,TVL,STD,,COLUMBIA COBRA TWIN VENT STORM DOOR,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,VKI,STOCK,,VENTILATOR KICKPANEL FOR KING ONELITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +TRUE,Iola,VKS,STOCK,,VENTILATOR SCREEN FOR KING ONE LITE,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,Iola,VP3700,VENTS,,VENT PANELS FOR #3700,,,,FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE,FALSE,FALSE,FALSE, +FALSE,Iola,WINDGAT,VACW,,ALLIANCE WINDGATE CASEMENT WINDOW,,,,FALSE,FALSE,FALSE,TRUE,FALSE,TRUE,FALSE,TRUE,FALSE,FALSE, +TRUE,Iola,XBUCKIN,INSERTS,,TEMPERED GLASS INSERTS FOR CROSSBUCKS,,,,FALSE,FALSE,TRUE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,Iola,ZBARS,STDMISC,,Z-BARS FOR STORM DOORS,,,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,KC,COBRATK,STD,,Cobra aluminum storm door with Glass Kick Panel,Door,Storm Door,,FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE,FALSE,FALSE,TRUE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, +FALSE,~,,,,,,,,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE, \ No newline at end of file diff --git a/app/data/products.json b/app/data/products.json new file mode 100644 index 0000000..fc37759 --- /dev/null +++ b/app/data/products.json @@ -0,0 +1,16794 @@ +[ + { + "id": "1650-10", + "productCode": "1650-10", + "category": "FPPW", + "description": "#1650-10 INSULATED FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "404", + "productCode": "404", + "category": "STORMS", + "description": "#404 FALCON STORM WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Storm Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "450", + "productCode": "450", + "category": "STORMS", + "description": "#450 RAVEN STORM WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Storm Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "606", + "productCode": "606", + "category": "STORMS", + "description": "#606 LION STORM WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Storm Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "650", + "productCode": "650", + "category": "STORMS", + "description": "#650 LYNX STORM WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Storm Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1400", + "productCode": "1400", + "category": "SHPW", + "description": "SERIES 1400 VINYL SLIDING PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Series 1400 Vinyl Sliding Primary", + "detailedDescription": "High-quality vinyl sliding window with excellent insulation properties. Perfect for residential applications.", + "features": [ + "Vinyl construction for durability", + "Smooth sliding operation", + "Energy efficient design", + "Low maintenance" + ], + "image": "https://images.unsplash.com/photo-1545259741-2ea3ebf61fa3?w=400&h=400&fit=crop", + "url": "/products/1400-vinyl-sliding", + "brochure": null, + "measureGuide": null + }, + { + "id": "1500", + "productCode": "1500", + "category": "SHPT", + "description": "SERIES 1500 S.H. TILT VINYL PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Primary Window" + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Series 1500 S.H. Tilt Vinyl Primary", + "detailedDescription": "Single hung window with convenient tilt feature for easy cleaning.", + "features": [ + "Tilt-in sashes for easy cleaning", + "Vinyl construction", + "Energy efficient", + "Security features" + ], + "image": null, + "url": "/products/1500-tilt-vinyl", + "brochure": null, + "measureGuide": null + }, + { + "id": "1510", + "productCode": "1510", + "category": "FPPW", + "description": "1510 VINYL INSULATED FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "1510 Vinyl Insulated Fixed Lite", + "detailedDescription": "Fixed window panel with insulated glass.", + "features": [ + "Non-operable design", + "Insulated glass", + "Vinyl frame", + "Low maintenance" + ], + "image": null, + "url": "/products/1510-fixed-lite", + "brochure": null, + "measureGuide": null + }, + { + "id": "1650", + "productCode": "1650", + "category": "SHPW", + "description": "#1650 INSULATED SINGLE HUNG", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "#1650 Insulated Single Hung", + "detailedDescription": "Premium insulated single hung window for maximum energy efficiency.", + "features": [ + "Superior insulation", + "Double-pane glass", + "Weatherstripping", + "Durable vinyl frame" + ], + "image": null, + "url": "/products/1650-insulated", + "brochure": null, + "measureGuide": null + }, + { + "id": "1700", + "productCode": "1700", + "category": "SHPW", + "description": "#1700 INSULATED SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "#1700 Insulated Slider", + "detailedDescription": "Insulated sliding window with smooth operation.", + "features": [ + "Insulated glass", + "Smooth gliding action", + "Multiple vent configurations", + "Screens available" + ], + "image": null, + "url": "/products/1700-slider", + "brochure": null, + "measureGuide": null + }, + { + "id": "1710", + "productCode": "1710", + "category": "FPPW", + "description": "C-1710 FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2000", + "productCode": "2000", + "category": "SHPT", + "description": "SERIES 2000 S.H. T.B. TILT PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Primary Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Series 2000 S.H. T.B. Tilt Primary", + "detailedDescription": "Thermal break single hung window with tilt feature.", + "features": [ + "Thermal break technology", + "Tilt-in sashes", + "Enhanced energy efficiency", + "Condensation resistant" + ], + "image": null, + "url": "/products/2000-thermal-break", + "brochure": null, + "measureGuide": null + }, + { + "id": "2100", + "productCode": "2100", + "category": "SHPT", + "description": "SERIES 2100 THERMAL BREAK SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Series 2100 Thermal Break Slider", + "detailedDescription": "Advanced thermal break slider for superior performance.", + "features": [ + "Thermal break construction", + "Energy Star rated", + "Heavy-duty rollers", + "Available in 3-panel configuration" + ], + "image": null, + "url": "/products/2100-thermal-slider", + "brochure": null, + "measureGuide": null + }, + { + "id": "2200", + "productCode": "2200", + "category": "FPPW", + "description": "SERIES 2200 T.B. FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Series 2200 T.B. Fixed Lite", + "detailedDescription": "Thermal break fixed lite window for maximum efficiency.", + "features": [ + "Thermal break frame", + "Fixed glass panel", + "Superior insulation", + "Tempered glass option available" + ], + "image": null, + "url": "/products/2200-thermal-fixed", + "brochure": null, + "measureGuide": null + }, + { + "id": "2650", + "productCode": "2650", + "category": "SHPW", + "description": "#2650 SINGLE HUNG SINGLE GLAZED PRIME", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2700", + "productCode": "2700", + "category": "SHPW", + "description": "#2700 SINGLE GLAZED SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "#2700 Single Glazed Slider", + "detailedDescription": "Single glazed sliding window for standard applications.", + "features": [ + "Cost-effective solution", + "Reliable operation", + "Multiple configurations", + "Durable construction" + ], + "image": null, + "url": "/products/2700-single-glazed", + "brochure": null, + "measureGuide": null + }, + { + "id": "2710", + "productCode": "2710", + "category": "FPPW", + "description": "C-2710 FIXED LITE - SINGLE GLAZED", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3000", + "productCode": "3000", + "category": "3000DHP", + "description": "SERIES 3000 D.H. T.B REPLACEMENT WD", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3100", + "productCode": "3100", + "category": "3000DHP", + "description": "SERIES 3100 T.B. REPLACEMENT SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Series 3100 T.B. Replacement Slider", + "detailedDescription": "Thermal break replacement slider for existing window openings.", + "features": [ + "Replacement window design", + "Thermal break technology", + "Easy installation", + "Energy efficient" + ], + "image": null, + "url": "/products/3100-replacement", + "brochure": null, + "measureGuide": null + }, + { + "id": "3200", + "productCode": "3200", + "category": "3000FPP", + "description": "SERIES 3200 T.B. PICTURE WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Series 3200 T.B. Picture Window", + "detailedDescription": "Large picture window with thermal break technology.", + "features": [ + "Expansive glass area", + "Thermal break construction", + "Unobstructed views", + "Energy efficient" + ], + "image": null, + "url": "/products/3200-picture-window", + "brochure": null, + "measureGuide": null + }, + { + "id": "3300", + "productCode": "3300", + "category": "CASEMNT", + "description": "3300 1-PANEL T.B. CASEMENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "3300 1-Panel T.B. Casement", + "detailedDescription": "Single panel casement window with thermal break.", + "features": [ + "Hinged operation", + "Thermal break frame", + "Full opening capability", + "Secure locking system" + ], + "image": null, + "url": "/products/3300-casement-1panel", + "brochure": null, + "measureGuide": null + }, + { + "id": "3302", + "productCode": "3302", + "category": "CASEMNT", + "description": "3300 2-PANEL T.B. CASEMENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "3300 2-Panel T.B. Casement", + "detailedDescription": "Two-panel casement window configuration.", + "features": [ + "Dual panels", + "Thermal break technology", + "Flexible ventilation", + "Energy efficient" + ], + "image": null, + "url": "/products/3302-casement-2panel", + "brochure": null, + "measureGuide": null + }, + { + "id": "3303", + "productCode": "3303", + "category": "CASEMNT", + "description": "3300 3-PANEL T.B. CASEMENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "3300 3-Panel T.B. Casement", + "detailedDescription": "Three-panel casement window for larger openings.", + "features": [ + "Triple panel design", + "Maximum ventilation", + "Thermal break construction", + "Architectural appeal" + ], + "image": null, + "url": "/products/3303-casement-3panel", + "brochure": null, + "measureGuide": null + }, + { + "id": "3310", + "productCode": "3310", + "category": "CASEMNT", + "description": "3310 FIXED CASEMENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "3310 Fixed Casement", + "detailedDescription": "Fixed casement window panel.", + "features": [ + "Non-operable design", + "Casement styling", + "Energy efficient", + "Durable construction" + ], + "image": null, + "url": "/products/3310-fixed-casement", + "brochure": null, + "measureGuide": null + }, + { + "id": "3700", + "productCode": "3700", + "category": "SHPW", + "description": "#3700 INSULATED SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3710", + "productCode": "3710", + "category": "FPPW", + "description": "C-3710 INSULATED FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "4700", + "productCode": "4700", + "category": "SHPW", + "description": "#4700 SINGLE GLAZED SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "4710", + "productCode": "4710", + "category": "FPPW", + "description": "#4710 SINGLE GLAZED FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "5200", + "productCode": "5200", + "category": "PD", + "description": "5200 VINYL PATIO DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White", + "Tan" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "5700", + "productCode": "5700", + "category": "SHPW", + "description": "#5700 INSULATED SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "6700", + "productCode": "6700", + "category": "SHPW", + "description": "#6700 SINGLE GLAZED SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "265010", + "productCode": "265010", + "category": "FPPW", + "description": "#2650-10 SINGLE GLAZED FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1400SCR", + "productCode": "1400SCR", + "category": "SHPW", + "description": "1400 SCREEN", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1650SCR", + "productCode": "1650SCR", + "category": "SCREENS", + "description": "SCREENS FOR #1650", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1650VS", + "productCode": "1650VS", + "category": "SHPW", + "description": "1650 BOTTOM SASH", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1700CSC", + "productCode": "1700CSC", + "category": "SCREENS", + "description": "SCREEN FOR #1700 CENTER VENT SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1700CV", + "productCode": "1700CV", + "category": "SHPW", + "description": "1700 CENTER VENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1700ESC", + "productCode": "1700ESC", + "category": "SCREENS", + "description": "SCREEN FOR #1700 ENDS VENT SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1700EV", + "productCode": "1700EV", + "category": "SHPW", + "description": "1700 END VENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1700SCR", + "productCode": "1700SCR", + "category": "SCREENS", + "description": "SCREEN FOR #1700 SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "1700VS", + "productCode": "1700VS", + "category": "SHPW", + "description": "#1700 VENT SASH", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2000SCR", + "productCode": "2000SCR", + "category": "SHPT", + "description": "C-2000 SCREEN", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2100EV", + "productCode": "2100EV", + "category": "2000SLI", + "description": "SERIES 2100 3-PANEL SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2200PDG", + "productCode": "2200PDG", + "category": "FPPW", + "description": "SERIES 2200 TB FIXED LITE W/TEMP GLASS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2650SCR", + "productCode": "2650SCR", + "category": "SCREENS", + "description": "SCREENS FOR 2650", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2650VP", + "productCode": "2650VP", + "category": "SHPW", + "description": "#2650 VENT PANEL - SINGLE HUNGE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2700CV", + "productCode": "2700CV", + "category": "SHPW", + "description": "C-2700 - CENTER VENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2700EV", + "productCode": "2700EV", + "category": "SHPW", + "description": "C-2700 - END VENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2700SCR", + "productCode": "2700SCR", + "category": "SCREENS", + "description": "SCREENS FOR #2700", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "2700VS", + "productCode": "2700VS", + "category": "SHPW", + "description": "#2700 VENT SASH", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3000SCR", + "productCode": "3000SCR", + "category": "SCREENS", + "description": "SCREENS FOR #3000", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "305INS", + "productCode": "305INS", + "category": "INSERTS", + "description": "#305 SASH WINDCHECK INSERTS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "306INS", + "productCode": "306INS", + "category": "INSERTS", + "description": "#306 SCHLEGEL GLASS INSERTS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3100EV", + "productCode": "3100EV", + "category": "3000DHP", + "description": "SERIES 3100 3-PANEL SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3100SCR", + "productCode": "3100SCR", + "category": "SCREENS", + "description": "SCREENS FOR #3100", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "310PSCR", + "productCode": "310PSCR", + "category": "SCRINS", + "description": "#310 PLAIN SCREENS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3700SCR", + "productCode": "3700SCR", + "category": "SCREENS", + "description": "SCREEN FOR #3700 POLE BARN WD", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "3700VP", + "productCode": "3700VP", + "category": "SHPW", + "description": "#3700 VENT PANEL", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "404ONE", + "productCode": "404ONE", + "category": "STPW", + "description": "#404 ONE-LITE ST WD", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "450ONE", + "productCode": "450ONE", + "category": "STPW", + "description": "#450 ONE-LITE ST WD", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "4700SCR", + "productCode": "4700SCR", + "category": "SCREENS", + "description": "SCREENS FOR #4700", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "606ONE", + "productCode": "606ONE", + "category": "STPW", + "description": "#606 ONE-LITE ST WD", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "6100I", + "productCode": "6100I", + "category": "STD", + "description": "STAR 6100 FULL VIEW STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "650ONE", + "productCode": "650ONE", + "category": "STPW", + "description": "#650 ONE-LITE ST WD", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "8100I", + "productCode": "8100I", + "category": "STD", + "description": "STAR 8100 FULL VIEW STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "BELMONT", + "productCode": "BELMONT", + "category": "3000FPP", + "description": "BELMONT ALLIANCE DOUBLE HUNG WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White", + "Tan" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "BGI", + "productCode": "BGI", + "category": "INSERTS", + "description": "BOTTOM GL INSERTS FOR ECONOMY ST WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "BGI404", + "productCode": "BGI404", + "category": "INSERTS", + "description": "BOTTOM GL INSERTS FOR #404/450 ST WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "BGI606", + "productCode": "BGI606", + "category": "INSERTS", + "description": "BOTTOM GL INSERTS FOR #606/650 ST WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "BGIST", + "productCode": "BGIST", + "category": "INSERTS", + "description": "INSERTS FOR STORM DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C-500", + "productCode": "C-500", + "category": "SHPW", + "description": "C-500 SH THERMAL BREAK INS.", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1150", + "productCode": "C1150", + "category": "CASEMNT", + "description": "C1150-VINYL AWNING WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1621", + "productCode": "C1621", + "category": "CIR TOP", + "description": "C-1621 INSULATED CIRCLE TOP", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1622", + "productCode": "C1622", + "category": "CIR TOP", + "description": "C-1622 INSULATED CIRCLE TOP", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1626VA", + "productCode": "C1626VA", + "category": "CIR TOP", + "description": "C-1626VA INSULATED CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1710", + "productCode": "C1710", + "category": "SHPW", + "description": "#1710 PICTURE OVER SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1721", + "productCode": "C1721", + "category": "CIR TOP", + "description": "C-1721 INSULATED CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1722", + "productCode": "C1722", + "category": "CIR TOP", + "description": "C-1722 INSULATED CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1724", + "productCode": "C1724", + "category": "CIR TOP", + "description": "C-1724 INSULATED CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C1800", + "productCode": "C1800", + "category": "C1800", + "description": "C-1800 INSIDE SLIDING STORM WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Storm Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C2021", + "productCode": "C2021", + "category": "CIR TOP", + "description": "C-2021 T.B CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C2022", + "productCode": "C2022", + "category": "CIR TOP", + "description": "C-2022 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C2026", + "productCode": "C2026", + "category": "CIR TOP", + "description": "C-2026 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C2710", + "productCode": "C2710", + "category": "SHPW", + "description": "#2710 PICTURE OVER SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C300", + "productCode": "C300", + "category": "BSMT", + "description": "C-300 ALUM INSERTS FOR BASEMENT BUCKS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C3221", + "productCode": "C3221", + "category": "CIR TOP", + "description": "C-3221 T.B CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C3222", + "productCode": "C3222", + "category": "CIR TOP", + "description": "C-3222 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C3223", + "productCode": "C3223", + "category": "CIR TOP", + "description": "C-3223 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C3224", + "productCode": "C3224", + "category": "CIR TOP", + "description": "C-3224 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C3300", + "productCode": "C3300", + "category": "CASEMNT", + "description": "3300 1-PANEL T.B. CASEMENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C3700", + "productCode": "C3700", + "category": "SHPW", + "description": "C-3700 INSULATED SLIDING POLE BARN WND", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C400", + "productCode": "C400", + "category": "BSMT", + "description": "C-400 VINYL INSERT FOR BASEMENT BUCKS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C500", + "productCode": "C500", + "category": "SHPW", + "description": "C-500 INS THERMAL BREAK SINGLE HUNG", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C500GL", + "productCode": "C500GL", + "category": "VENTS", + "description": "LITES OF INSULATED GLASS FOR C-500'S", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C500GLP", + "productCode": "C500GLP", + "category": "VENTS", + "description": "LITES OF INSULATED GLASS FOR C-500'S", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C500PP", + "productCode": "C500PP", + "category": "SHPW", + "description": "C-500 INS THERMAL BREAK SINGLE HUNG", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C500SCR", + "productCode": "C500SCR", + "category": "SCREENS", + "description": "C-500 SCREEN", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C500VP", + "productCode": "C500VP", + "category": "VENTS", + "description": "VENT PANELS FOR C-500", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C521", + "productCode": "C521", + "category": "CIR TOP", + "description": "C-521 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C522", + "productCode": "C522", + "category": "CIR TOP", + "description": "C-522 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C526", + "productCode": "C526", + "category": "CIR TOP", + "description": "C-526 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C900", + "productCode": "C900", + "category": "SHPW", + "description": "C-900 INS THERMAL BREAK SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C900EV", + "productCode": "C900EV", + "category": "SHPW", + "description": "C-900 ENDS VENT SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C900SCR", + "productCode": "C900SCR", + "category": "SCREENS", + "description": "SCREEN FOR C-900", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C910", + "productCode": "C910", + "category": "FPPW", + "description": "C-910 INSULATED T.B. FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C910PP", + "productCode": "C910PP", + "category": "FPPW", + "description": "C-910 ARCH TOP", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C921", + "productCode": "C921", + "category": "CIR TOP", + "description": "C-921 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C922", + "productCode": "C922", + "category": "CIR TOP", + "description": "C-922 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C924", + "productCode": "C924", + "category": "CIR TOP", + "description": "C-924 T.B. CIRCLE TOPS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C931", + "productCode": "C931", + "category": "CIR TOP", + "description": "C-931 T.B. ROUND PRIMARY WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C939", + "productCode": "C939", + "category": "CIR TOP", + "description": "C-939 T.B. ROUND PRIMARY WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C940", + "productCode": "C940", + "category": "CIR TOP", + "description": "C-940 T.B. OCTAGON PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C949", + "productCode": "C949", + "category": "CIR TOP", + "description": "C949 OCTAGON THERMAL BREAK WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "C960", + "productCode": "C960", + "category": "FPPW", + "description": "C-960 INSULATED T.B. CIRCLE TOP", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "COBRAI", + "productCode": "COBRAI", + "category": "STD", + "description": "COLUMBIA COBRA STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "sizes": [ + {"value": "30x81", "label": "30\" x 81\""}, + {"value": "32x81", "label": "32\" x 81\""}, + {"value": "36x81", "label": "36\" x 81\""} + ], + "isAccessory": false, + "compatibleAccessories": [], + "flatImages": true, + "title": "Cobra Aluminum Storm Door", + "detailedDescription": "The Columbia Cobra aluminum storm door has a 1-1/4\u2033 master frame. Double-track door with removable inserts. Available in black, bronze, sandstone or white baked on enamel finish.", + "features": [ + "1-1/4\u2033 master frame", + "Double-track with removable inserts", + "Baked on enamel finish", + "Available in Black, Bronze, Sandstone, White", + "Standard sizes: 2\u2032-6\u2033 x 6\u2032-8\u2033, 2\u2032-8\u2033 x 6\u2032-8\u2033, 3\u2032-0\u2033 x 6\u2032-8\u2033", + "Custom sizes available by special order" + ], + "image": "https://columbiawindows.com/wp-content/uploads/2014/10/Cobra-Ali-Storm-Door.jpg", + "url": "https://columbiawindows.com/product-detail/cobra-aluminum-storm-doors-1100/", + "brochure": "https://columbiawindows.com./wp-content/uploads/2014/10/Accessories-08-18-2010s.pdf", + "measureGuide": "https://columbiawindows.com./wp-content/uploads/2014/10/How-to-Measure2.pdf" + }, + { + "id": "CRWNFVI", + "productCode": "CRWNFVI", + "category": "STD", + "description": "CROWN FULL VIEW STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "CRWNSDI", + "productCode": "CRWNSDI", + "category": "STD", + "description": "COLUMBIA CROWN SCREEN DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "D770", + "productCode": "D770", + "category": "SHPT", + "description": "D770 D.H. TILT VINYL PRIMARY WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Primary Window" + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "DSGLASS", + "productCode": "DSGLASS", + "category": "GLASS", + "description": "DOUBLE STRENGTH GLASS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "DURASEA", + "productCode": "DURASEA", + "category": "FPPW", + "description": "DURASEAL 5/8\" (GRAY) PER REEL", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "EXPAND", + "productCode": "EXPAND", + "category": "EXPANDR", + "description": "SILL EXPANDERS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "FULLSCR", + "productCode": "FULLSCR", + "category": "SCREENS", + "description": "FULL SCREEN", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "FVGI", + "productCode": "FVGI", + "category": "INSERTS", + "description": "GLASS INSERTS FOR KING ONE LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "FVSI", + "productCode": "FVSI", + "category": "INSERTS", + "description": "FULL SCREENS ONLY FOR KING ONE-LITES", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "GOLIATH", + "productCode": "GOLIATH", + "category": "STD", + "description": "GOLIATH STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "HERCULE", + "productCode": "HERCULE", + "category": "STD", + "description": "HERCULES STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "IMPERIL", + "productCode": "IMPERIL", + "category": "PD", + "description": "IMPERIAL PATIO DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "INSGLAS", + "productCode": "INSGLAS", + "category": "GLASS", + "description": "INSULATED GLASS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "ISP", + "productCode": "ISP", + "category": "PD", + "description": "STATIONARY PANELS FOR IMPERIAL DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "IVP", + "productCode": "IVP", + "category": "PD", + "description": "VENT PANELS FOR IMPERIAL PATIO DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "JET", + "productCode": "JET", + "category": "PD", + "description": "COLUMBIA JET PATIO DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "JSP", + "productCode": "JSP", + "category": "PD", + "description": "STATIONARY PANELS FOR JET DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "JVP", + "productCode": "JVP", + "category": "PD", + "description": "VENT PANELS FOR JET PATIO DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "KINGDVI", + "productCode": "KINGDVI", + "category": "STD", + "description": "KING DUAL VENT STORM DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "KINGFSC", + "productCode": "KINGFSC", + "category": "INSERTS", + "description": "FULL SCREEN FOR KING ONE-LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "KINGI", + "productCode": "KINGI", + "category": "STD", + "description": "KING ONE-LITE STORM DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "KINGSDI", + "productCode": "KINGSDI", + "category": "STD", + "description": "KING ONE-LITE SCREEN DOOR ONLY", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "M1200", + "productCode": "M1200", + "category": "PD", + "description": "M1200 PATIO STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "M306", + "productCode": "M306", + "category": "INSERTS", + "description": "M-306 SCHLEGEL GLASS INSERTS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "OUTSIDE", + "productCode": "OUTSIDE", + "category": "STDMISC", + "description": "OUTSIDE DOOR SWEEPS - ALUM + VINYL", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PATIOSC", + "productCode": "PATIOSC", + "category": "SCREENS", + "description": "PATIO DOOR SCREENS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PDSCRTT", + "productCode": "PDSCRTT", + "category": "SCREENS", + "description": "SPECIAL SIZE SCREENS FOR PATIO DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PRPDS", + "productCode": "PRPDS", + "category": "SCREENS", + "description": "SCREENS MADE FROM PLAIN PATIO SCR RAIL", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PRSCR", + "productCode": "PRSCR", + "category": "PSCREEN", + "description": "SCREENS FOR PRIME MADE FROM #19-88", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Tan", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PRSCR11", + "productCode": "PRSCR11", + "category": "PSCREEN", + "description": "SCREEN FOR PRIME MADE FROM #19-11", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PSINS", + "productCode": "PSINS", + "category": "INSERTS", + "description": "PLAIN SASH INSERTS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PWS", + "productCode": "PWS", + "category": "STPW", + "description": "PIN-ON PICTURE WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Tan", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "PWSINS", + "productCode": "PWSINS", + "category": "INSERTS", + "description": "INSERTS ONLY FOR PIN-ON PICTURE WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R1150", + "productCode": "R1150", + "category": "REPLACE", + "description": "R-1150 VINYL AWNING WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R1400", + "productCode": "R1400", + "category": "REPLACE", + "description": "SERIES 1400 VINYL SLIDING PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Primary Window" + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R1500", + "productCode": "R1500", + "category": "REPLACE", + "description": "SERIES 1500 S.H. TILT VINYL PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Primary Window" + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R1510", + "productCode": "R1510", + "category": "FPPW", + "description": "SERIES 1510 FIXED LITE VINYL PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R2000", + "productCode": "R2000", + "category": "REPLACE", + "description": "SERIES 2000 S.H. T.B. TILT PRIMARY", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Primary Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R2100", + "productCode": "R2100", + "category": "REPLACE", + "description": "SERIES 2100 THERMAL BREAK SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R2100EV", + "productCode": "R2100EV", + "category": "R2000SL", + "description": "SERIES 2100 3-PANEL T.B. SLIDER", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R2200", + "productCode": "R2200", + "category": "RFPPW", + "description": "SERIES 2200 T.B. FIXED LITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R300", + "productCode": "R300", + "category": "RBSMT", + "description": "C-300 ALUM INSERTS FOR BASEMENT BUCKS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R3302", + "productCode": "R3302", + "category": "CASEMNT", + "description": "3302 2-PANEL T.B. CASEMENT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R400", + "productCode": "R400", + "category": "RBSMT", + "description": "C-400 VINYL INSERT FOR BASEMENT BUCKS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R770", + "productCode": "R770", + "category": "REPLACE", + "description": "R770 D.H. TILT VINYL PRIMARY WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Primary Window" + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "R770SCR", + "productCode": "R770SCR", + "category": "SCREENS", + "description": "FULL SCREEN", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "RCKTRAP", + "productCode": "RCKTRAP", + "category": "INSERTS", + "description": "ROCKET TRAPEZOIDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "REWIRE", + "productCode": "REWIRE", + "category": "SCREENS", + "description": "REWIRED SCREENS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "RNDROCK", + "productCode": "RNDROCK", + "category": "INSERTS", + "description": "ROUND ROCKET INSERT", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "ROCKET", + "productCode": "ROCKET", + "category": "INSERTS", + "description": "ROCKET INSERTS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "RROCKET", + "productCode": "RROCKET", + "category": "INSERTS", + "description": "RADIUS ROCKETS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "SCRI", + "productCode": "SCRI", + "category": "INSERTS", + "description": "SCREEN INSERTS FOR ECONOMY STORM WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "SCRI404", + "productCode": "SCRI404", + "category": "INSERTS", + "description": "SCREEN INSERTS FOR #404/450 STORM WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "SCRI606", + "productCode": "SCRI606", + "category": "INSERTS", + "description": "SCREEN INSERTS FOR #606/650 STORM WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "SCRI808", + "productCode": "SCRI808", + "category": "INSERTS", + "description": "SCREEN INSERTS FOR #808/850 STORM WIND", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "SSGLASS", + "productCode": "SSGLASS", + "category": "GLASS", + "description": "SINGLE STRENGTH GLASS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "SSSCR", + "productCode": "SSSCR", + "category": "INSERTS", + "description": "SCREEN INSERT FOR SELF STORING DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "TBGI", + "productCode": "TBGI", + "category": "INSERTS", + "description": "TEMPERED GLASS INSERTS FOR SS DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "TBR", + "productCode": "TBR", + "category": "PD", + "description": "IMPERIAL T.B.R. REPLACEMENT PATIO DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Patio Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "TGI", + "productCode": "TGI", + "category": "INSERTS", + "description": "TOP GL INSERTS FOR ECONOMY ST WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze", + "Mill" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "TGI404", + "productCode": "TGI404", + "category": "INSERTS", + "description": "TOP GLASS INSERTS FOR #404/450 ST WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "TGI606", + "productCode": "TGI606", + "category": "INSERTS", + "description": "TOP GLASS INSERTS FOR #606/650 ST WDS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Mill", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "TGIODD", + "productCode": "TGIODD", + "category": "INSERTS", + "description": "TOP GLASS INSERTS FOR STORM DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "THOR", + "productCode": "THOR", + "category": "STD", + "description": "THOR - STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "TVI", + "productCode": "TVI", + "category": "STD", + "description": "COLUMBIA COBRA TWIN VENT STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [], + "title": "Cobra Aluminum Storm Door", + "detailedDescription": "The Columbia Cobra aluminum storm door has a 1-1/4\u2033 master frame. Double-track door with removable inserts. Available in black, bronze, sandstone or white baked on enamel finish.", + "features": [ + "1-1/4\u2033 master frame", + "Double-track with removable inserts", + "Baked on enamel finish", + "Available in Black, Bronze, Sandstone, White", + "Standard sizes: 2\u2032-6\u2033 x 6\u2032-8\u2033, 2\u2032-8\u2033 x 6\u2032-8\u2033, 3\u2032-0\u2033 x 6\u2032-8\u2033", + "Custom sizes available by special order" + ], + "image": "https://columbiawindows.com/wp-content/uploads/2014/10/Cobra-Ali-Storm-Door.jpg", + "url": "https://columbiawindows.com/product-detail/cobra-aluminum-storm-doors-1100/", + "brochure": "https://columbiawindows.com./wp-content/uploads/2014/10/Accessories-08-18-2010s.pdf", + "measureGuide": "https://columbiawindows.com./wp-content/uploads/2014/10/How-to-Measure2.pdf" + }, + { + "id": "VKI", + "productCode": "VKI", + "category": "STOCK", + "description": "VENTILATOR KICKPANEL FOR KING ONELITE", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "VP3700", + "productCode": "VP3700", + "category": "VENTS", + "description": "VENT PANELS FOR #3700", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "White", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "WINDGAT", + "productCode": "WINDGAT", + "category": "VACW", + "description": "ALLIANCE WINDGATE CASEMENT WINDOW", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Vinyl" + ], + "colors": [ + "White", + "Tan" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "ZBARS", + "productCode": "ZBARS", + "category": "STDMISC", + "description": "Z-BARS FOR STORM DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "COBRATK", + "productCode": "COBRATK", + "category": "STD", + "description": "Cobra aluminum storm door with Glass Kick Panel", + "discontinued": false, + "location": "KC", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + }, + { + "id": "", + "productCode": "", + "category": "", + "description": "", + "discontinued": false, + "location": "~", + "baseType": "", + "subType": { + "door": null, + "window": null + }, + "materials": [], + "colors": [], + "isAccessory": false, + "compatibleAccessories": [] + } +] \ No newline at end of file diff --git a/app/data/questions.json b/app/data/questions.json new file mode 100644 index 0000000..0ca0578 --- /dev/null +++ b/app/data/questions.json @@ -0,0 +1,523 @@ +{ + "start": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "notes": "

Quick Tips:

  • Press Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac) to hard refresh the page
  • Press F5 to reload the application
  • Use the back button or breadcrumbs to navigate
", + "answers": [ + { + "caption": "Window", + "image": "🪟", + "next": "q-dimensions" + }, + { + "caption": "Door", + "image": "🚪", + "next": "q-dimensions" + }, + { + "caption": "Patio Door", + "image": "🚪", + "next": "q-dimensions" + } + ] + }, + "q-dimensions": { + "type": "question", + "inputType": "form", + "title": "Select Storm Door Size", + "subtitle": "Choose a standard size or enter custom dimensions", + "notes": "

Need help measuring?

For accurate measurements, please refer to our How to Measure Guide.

Tips:

  • Measure the rough opening, not the existing unit
  • Measure width at the top, middle, and bottom - use the smallest measurement
  • Measure height on the left, center, and right - use the smallest measurement
  • Round down to the nearest inch
", + "measurementType": { + "defaultValue": "opening-size", + "options": [ + { + "value": "opening-size", + "label": "Opening Size", + "disabled": false + }, + { + "value": "tip-to-tip", + "label": "Tip-to-tip", + "disabled": false + } + ] + }, + "fields": [ + { + "name": "width", + "label": "Width (inches)", + "type": "number", + "required": true, + "placeholder": "26" + }, + { + "name": "height", + "label": "Height (inches)", + "type": "number", + "required": true, + "placeholder": "81" + } + ], + "next": "q-material" + }, + "q-material": { + "type": "question", + "inputType": "form", + "title": "Material & Color Preferences", + "subtitle": "Customize your selection", + "fields": [ + { + "name": "material", + "label": "Material", + "type": "select", + "required": true, + "options": [ + { "value": "", "label": "-- Select Material --" }, + { "value": "vinyl", "label": "Vinyl" }, + { "value": "aluminum", "label": "Aluminum" } + ] + }, + { + "name": "color", + "label": "Color", + "type": "text", + "required": false, + "placeholder": "e.g., White, Beige, etc." + } + ], + "next": "q-material-conditional" + }, + "q-window-type": { + "type": "question", + "inputType": "button", + "title": "What type of window?", + "subtitle": "Select the category that best matches your needs", + "answers": [ + { + "caption": "Single Hung Window", + "image": "🪟", + "next": "q-single-hung" + }, + { + "caption": "Slider Window", + "image": "ā†”ļø", + "next": "q-slider" + }, + { + "caption": "Fixed Lite", + "image": "šŸ”²", + "next": "q-fixed" + }, + { + "caption": "Casement Window", + "image": "🚪", + "next": "q-casement" + } + ] + }, + "q-door-type": { + "type": "question", + "inputType": "button", + "title": "What type of door?", + "subtitle": "Select your door style", + "answers": [ + { + "caption": "Entry Door", + "image": "🚪", + "next": "prod-entry-door" + }, + { + "caption": "Storm Door", + "image": "🚪", + "next": "prod-cobra-1100" + }, + { + "caption": "Patio Door", + "image": "🚪", + "next": "q-patio" + } + ] + }, + "q-single-hung": { + "type": "question", + "inputType": "button", + "title": "What features do you need?", + "subtitle": "Choose your single hung window configuration", + "answers": [ + { + "caption": "Vinyl Sliding Primary", + "image": "🪟", + "next": "prod-1400" + }, + { + "caption": "Tilt Vinyl Primary", + "image": "🪟", + "next": "prod-1500" + }, + { + "caption": "Insulated Single Hung", + "image": "🪟", + "next": "prod-1650" + }, + { + "caption": "Thermal Break Tilt", + "image": "🪟", + "next": "prod-2000" + } + ] + }, + "q-slider": { + "type": "question", + "inputType": "button", + "title": "What type of slider do you need?", + "subtitle": "Select your slider window configuration", + "answers": [ + { + "caption": "Insulated Slider", + "image": "ā†”ļø", + "next": "prod-1700" + }, + { + "caption": "Thermal Break Slider", + "image": "ā†”ļø", + "next": "prod-2100" + }, + { + "caption": "Single Glazed Slider", + "image": "ā†”ļø", + "next": "prod-2700" + }, + { + "caption": "Replacement Slider", + "image": "ā†”ļø", + "next": "prod-3100" + } + ] + }, + "q-fixed": { + "type": "question", + "inputType": "button", + "title": "Which fixed lite option?", + "subtitle": "Choose your fixed window configuration", + "answers": [ + { + "caption": "Vinyl Insulated Fixed", + "image": "šŸ”²", + "next": "prod-1510" + }, + { + "caption": "Thermal Break Fixed", + "image": "šŸ”²", + "next": "prod-2200" + }, + { + "caption": "Picture Window", + "image": "šŸ”²", + "next": "prod-3200" + } + ] + }, + "q-casement": { + "type": "question", + "inputType": "button", + "title": "How many panels?", + "subtitle": "Select the number of casement panels", + "answers": [ + { + "caption": "1-Panel Casement", + "image": "🚪", + "next": "prod-3300" + }, + { + "caption": "2-Panel Casement", + "image": "🚪", + "next": "prod-3302" + }, + { + "caption": "3-Panel Casement", + "image": "🚪", + "next": "prod-3303" + }, + { + "caption": "Fixed Casement", + "image": "🚪", + "next": "prod-3310" + } + ] + }, + "q-patio": { + "type": "question", + "inputType": "button", + "title": "Select your patio door", + "subtitle": "Choose the right patio door for your space", + "answers": [ + { + "caption": "Royal Crown Patio Door", + "image": "🚪", + "next": "prod-2400" + } + ] + }, + "prod-entry-door": { + "type": "product", + "code": "ENTRY-001", + "title": "Custom Entry Door", + "category": "DOOR", + "image": "https://images.unsplash.com/photo-1572025442646-866d16c84a54?w=400&h=400&fit=crop", + "url": "/products/entry-door", + "description": "Custom entry door based on your specifications.", + "features": [ + "Custom dimensions", + "Choice of materials", + "Security features", + "Weather resistant" + ] + }, + "prod-cobra-1100": { + "type": "product", + "code": "1100", + "title": "Cobra Aluminum Storm Door", + "category": "STORM DOOR", + "image": "https://columbiawindows.com/wp-content/uploads/2014/10/Cobra-Ali-Storm-Door.jpg", + "url": "https://columbiawindows.com/product-detail/cobra-aluminum-storm-doors-1100/", + "description": "The Columbia Cobra aluminum storm door has a 1-1/4″ master frame. Double-track door with removable inserts. Available in black, bronze, sandstone or white baked on enamel finish.", + "features": [ + "1-1/4″ master frame", + "Double-track with removable inserts", + "Baked on enamel finish", + "Available in Black, Bronze, Sandstone, White", + "Standard sizes: 2′-6″ x 6′-8″, 2′-8″ x 6′-8″, 3′-0″ x 6′-8″", + "Custom sizes available by special order" + ], + "brochure": "https://columbiawindows.com./wp-content/uploads/2014/10/Accessories-08-18-2010s.pdf", + "measureGuide": "https://columbiawindows.com./wp-content/uploads/2014/10/How-to-Measure2.pdf" + }, + "prod-1400": { + "type": "product", + "code": "1400", + "title": "Series 1400 Vinyl Sliding Primary", + "category": "SHPW", + "image": "https://images.unsplash.com/photo-1545259741-2ea3ebf61fa3?w=400&h=400&fit=crop", + "url": "/products/1400-vinyl-sliding", + "description": "High-quality vinyl sliding window with excellent insulation properties. Perfect for residential applications.", + "features": [ + "Vinyl construction for durability", + "Smooth sliding operation", + "Energy efficient design", + "Low maintenance" + ] + }, + "prod-1500": { + "type": "product", + "code": "1500", + "title": "Series 1500 S.H. Tilt Vinyl Primary", + "category": "SHPT", + "url": "/products/1500-tilt-vinyl", + "description": "Single hung window with convenient tilt feature for easy cleaning.", + "features": [ + "Tilt-in sashes for easy cleaning", + "Vinyl construction", + "Energy efficient", + "Security features" + ] + }, + "prod-1650": { + "type": "product", + "code": "1650", + "title": "#1650 Insulated Single Hung", + "category": "SHPW", + "url": "/products/1650-insulated", + "description": "Premium insulated single hung window for maximum energy efficiency.", + "features": [ + "Superior insulation", + "Double-pane glass", + "Weatherstripping", + "Durable vinyl frame" + ] + }, + "prod-2000": { + "type": "product", + "code": "2000", + "title": "Series 2000 S.H. T.B. Tilt Primary", + "category": "SHPT", + "url": "/products/2000-thermal-break", + "description": "Thermal break single hung window with tilt feature.", + "features": [ + "Thermal break technology", + "Tilt-in sashes", + "Enhanced energy efficiency", + "Condensation resistant" + ] + }, + "prod-1700": { + "type": "product", + "code": "1700", + "title": "#1700 Insulated Slider", + "category": "SHPW", + "url": "/products/1700-slider", + "description": "Insulated sliding window with smooth operation.", + "features": [ + "Insulated glass", + "Smooth gliding action", + "Multiple vent configurations", + "Screens available" + ] + }, + "prod-2100": { + "type": "product", + "code": "2100", + "title": "Series 2100 Thermal Break Slider", + "category": "SHPT", + "url": "/products/2100-thermal-slider", + "description": "Advanced thermal break slider for superior performance.", + "features": [ + "Thermal break construction", + "Energy Star rated", + "Heavy-duty rollers", + "Available in 3-panel configuration" + ] + }, + "prod-2700": { + "type": "product", + "code": "2700", + "title": "#2700 Single Glazed Slider", + "category": "SHPW", + "url": "/products/2700-single-glazed", + "description": "Single glazed sliding window for standard applications.", + "features": [ + "Cost-effective solution", + "Reliable operation", + "Multiple configurations", + "Durable construction" + ] + }, + "prod-3100": { + "type": "product", + "code": "3100", + "title": "Series 3100 T.B. Replacement Slider", + "category": "3000DHP", + "url": "/products/3100-replacement", + "description": "Thermal break replacement slider for existing window openings.", + "features": [ + "Replacement window design", + "Thermal break technology", + "Easy installation", + "Energy efficient" + ] + }, + "prod-1510": { + "type": "product", + "code": "1510", + "title": "1510 Vinyl Insulated Fixed Lite", + "category": "FPPW", + "url": "/products/1510-fixed-lite", + "description": "Fixed window panel with insulated glass.", + "features": [ + "Non-operable design", + "Insulated glass", + "Vinyl frame", + "Low maintenance" + ] + }, + "prod-2200": { + "type": "product", + "code": "2200", + "title": "Series 2200 T.B. Fixed Lite", + "category": "FPPW", + "url": "/products/2200-thermal-fixed", + "description": "Thermal break fixed lite window for maximum efficiency.", + "features": [ + "Thermal break frame", + "Fixed glass panel", + "Superior insulation", + "Tempered glass option available" + ] + }, + "prod-3200": { + "type": "product", + "code": "3200", + "title": "Series 3200 T.B. Picture Window", + "category": "3000FPP", + "url": "/products/3200-picture-window", + "description": "Large picture window with thermal break technology.", + "features": [ + "Expansive glass area", + "Thermal break construction", + "Unobstructed views", + "Energy efficient" + ] + }, + "prod-3300": { + "type": "product", + "code": "3300", + "title": "3300 1-Panel T.B. Casement", + "category": "CASEMNT", + "url": "/products/3300-casement-1panel", + "description": "Single panel casement window with thermal break.", + "features": [ + "Hinged operation", + "Thermal break frame", + "Full opening capability", + "Secure locking system" + ] + }, + "prod-3302": { + "type": "product", + "code": "3302", + "title": "3300 2-Panel T.B. Casement", + "category": "CASEMNT", + "url": "/products/3302-casement-2panel", + "description": "Two-panel casement window configuration.", + "features": [ + "Dual panels", + "Thermal break technology", + "Flexible ventilation", + "Energy efficient" + ] + }, + "prod-3303": { + "type": "product", + "code": "3303", + "title": "3300 3-Panel T.B. Casement", + "category": "CASEMNT", + "url": "/products/3303-casement-3panel", + "description": "Three-panel casement window for larger openings.", + "features": [ + "Triple panel design", + "Maximum ventilation", + "Thermal break construction", + "Architectural appeal" + ] + }, + "prod-3310": { + "type": "product", + "code": "3310", + "title": "3310 Fixed Casement", + "category": "CASEMNT", + "url": "/products/3310-fixed-casement", + "description": "Fixed casement window panel.", + "features": [ + "Non-operable design", + "Casement styling", + "Energy efficient", + "Durable construction" + ] + }, + "prod-2400": { + "type": "product", + "code": "2400", + "title": "2400 Royal Crown Patio Door", + "category": "PD", + "image": "https://images.unsplash.com/photo-1585412727339-b82d6d394f51?w=400&h=400&fit=crop", + "url": "/products/2400-patio-door", + "description": "Premium patio door system with superior performance.", + "features": [ + "Wide opening", + "Smooth operation", + "Security features", + "Energy efficient glass" + ] + } +} diff --git a/app/data/users.json b/app/data/users.json new file mode 100644 index 0000000..1a176c8 --- /dev/null +++ b/app/data/users.json @@ -0,0 +1,98 @@ +[ + { + "username": "Master", + "password": "pbkdf2:sha256:1000000$tPRmseRGKLveXTxS$87eda9a20db29d12838fcb607f1f3536141f743ec9879702dc2a0dd893b4078d", + "defaultLocation": "KC", + "permissions": { + "manage_users": true, + "view_reports": true, + "create_quotes": true, + "approve_quotes": true, + "manage_products": true, + "manage_inventory": true + }, + "locationSettings": { + "LINDS": { + "accessible": true + }, + "IOLA": { + "accessible": true + }, + "KC": { + "accessible": true + }, + "BMD": { + "accessible": true + } + }, + "active": true + }, + { + "username": "Darlene", + "password": "pbkdf2:sha256:1000000$8sblSWLWIvsjcta2$d974d13956f67cc42e1fa88191d68c896d4daa663ae2d76d7988beb569647d27", + "defaultLocation": "IOLA", + "permissions": { + "manage_users": false, + "view_reports": true, + "create_quotes": true, + "approve_quotes": false, + "manage_products": false, + "manage_inventory": false + }, + "locationSettings": { + "LINDS": { + "accessible": true + }, + "IOLA": { + "accessible": true + }, + "KC": { + "accessible": true + }, + "BMD": { + "accessible": false + } + }, + "active": true + }, + { + "username": "Toni", + "password": "pbkdf2:sha256:1000000$5k3daJKZt7hqNAkq$e77819a2b2ee4bf9955dc10b3b4da7b14de2b4ecda5ce5f553daf3783b0a316c", + "defaultLocation": "LINDS", + "locationSettings": { + "LINDS": { + "accessible": true + }, + "IOLA": { + "accessible": false + }, + "KC": { + "accessible": false + }, + "BMD": { + "accessible": false + } + }, + "active": true + }, + { + "username": "Jason", + "password": "pbkdf2:sha256:1000000$rNSYLqRWZ6tdny1G$cad2afda9193b7cb50b9e9423f95ff83bc14428c8a2d98607cd6e5ce17cb70c7", + "defaultLocation": "KC", + "locationSettings": { + "LINDS": { + "accessible": true + }, + "IOLA": { + "accessible": true + }, + "KC": { + "accessible": true + }, + "BMD": { + "accessible": true + } + }, + "active": false + } +] \ No newline at end of file diff --git a/app/debug_startup.py b/app/debug_startup.py new file mode 100644 index 0000000..d814c1e --- /dev/null +++ b/app/debug_startup.py @@ -0,0 +1,111 @@ +""" +Debug startup script - writes detailed logs to file +Upload this and temporarily set it as the startup file to diagnose issues +""" +import sys +import os +from datetime import datetime + +# Create log file +log_file = '/home/bmdwtjuw/product-finder/startup_debug.log' + +def log(message): + with open(log_file, 'a') as f: + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + f.write(f"[{timestamp}] {message}\n") + +try: + log("="*70) + log("STARTUP DEBUG - BEGIN") + log("="*70) + + # Python info + log(f"Python version: {sys.version}") + log(f"Python executable: {sys.executable}") + log(f"Current directory: {os.getcwd()}") + + # Environment variables + log("\nEnvironment Variables:") + log(f" APPLICATION_ROOT: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}") + log(f" FLASK_ENV: {os.environ.get('FLASK_ENV', 'NOT SET')}") + + # Python path + log(f"\nPython path: {sys.path}") + + # Check Flask installation + log("\nChecking Flask installation...") + try: + import flask + log(f" āœ“ Flask version: {flask.__version__ if hasattr(flask, '__version__') else 'unknown'}") + except ImportError as e: + log(f" āœ— Flask NOT installed: {e}") + log(" RUN: pip install Flask==3.0.0 Werkzeug==3.0.1") + + # Check Werkzeug + try: + import werkzeug + log(f" āœ“ Werkzeug version: {werkzeug.__version__ if hasattr(werkzeug, '__version__') else 'unknown'}") + except ImportError as e: + log(f" āœ— Werkzeug NOT installed: {e}") + + # Check if app.py exists + log("\nChecking files...") + app_py_path = os.path.join(os.path.dirname(__file__), 'app.py') + if os.path.exists(app_py_path): + log(f" āœ“ app.py exists at {app_py_path}") + else: + log(f" āœ— app.py NOT FOUND at {app_py_path}") + + # Try importing app + log("\nAttempting to import Flask app...") + try: + from app import app + log(f" āœ“ Successfully imported Flask app") + log(f" APPLICATION_ROOT config: {app.config.get('APPLICATION_ROOT', 'NOT SET')}") + + # Count routes + routes = list(app.url_map.iter_rules()) + log(f" āœ“ Registered routes: {len(routes)}") + + # Create WSGI application + def application(environ, start_response): + log(f"\nRequest received: {environ.get('PATH_INFO', '/')}") + return app(environ, start_response) + + log("\nāœ“ APPLICATION READY - Check site now") + log("="*70) + + except Exception as e: + log(f" āœ— Failed to import app:") + log(f" Error: {e}") + + import traceback + log(f"\nFull traceback:") + log(traceback.format_exc()) + + # Create error application + def application(environ, start_response): + status = '500 Internal Server Error' + output = f'Import Error - Check {log_file}\n\n{traceback.format_exc()}'.encode('utf-8') + response_headers = [('Content-type', 'text/plain'), + ('Content-Length', str(len(output)))] + start_response(status, response_headers) + return [output] + + log("="*70) + +except Exception as e: + # Catch-all for any errors + import traceback + log(f"\nFATAL ERROR during startup:") + log(str(e)) + log(traceback.format_exc()) + log("="*70) + + def application(environ, start_response): + status = '500 Internal Server Error' + output = f'Startup Error - Check {log_file}'.encode('utf-8') + response_headers = [('Content-type', 'text/plain'), + ('Content-Length', str(len(output)))] + start_response(status, response_headers) + return [output] diff --git a/app/fix_passenger_file.py b/app/fix_passenger_file.py new file mode 100644 index 0000000..2f1883c --- /dev/null +++ b/app/fix_passenger_file.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +""" +Fix Passenger Configuration +============================ +This script renames the corrupted passenger_wsgi.py file so Passenger +will use the control panel settings instead. +""" +import os +import sys + +def main(): + print("=" * 70) + print("FIXING PASSENGER CONFIGURATION") + print("=" * 70) + + # Check if passenger_wsgi.py exists + if os.path.exists('passenger_wsgi.py'): + print("\nFound passenger_wsgi.py - this file is causing the problem") + print("Renaming it to passenger_wsgi.py.DISABLED...") + + try: + os.rename('passenger_wsgi.py', 'passenger_wsgi.py.DISABLED') + print("SUCCESS: passenger_wsgi.py renamed to passenger_wsgi.py.DISABLED") + print("\nPassenger will now use the control panel settings:") + print(" Application startup file: wsgi.py") + print(" Application Entry point: app") + except Exception as e: + print(f"ERROR: Could not rename file: {e}") + return 1 + else: + print("\npassenger_wsgi.py not found - already fixed or doesn't exist") + + print("\n" + "=" * 70) + print("NEXT STEPS:") + print("=" * 70) + print("1. Restart your Python application in the control panel") + print("2. Visit: https://columbiawindows.com/product-finder/test") + print("3. If it works, you're done!") + print("=" * 70) + + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/app/fix_passenger_lock.sh b/app/fix_passenger_lock.sh new file mode 100644 index 0000000..60813da --- /dev/null +++ b/app/fix_passenger_lock.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Fix Passenger lock file permissions +# Run this on the server to resolve "Can't acquire lock" error + +echo "==========================================" +echo "FIXING PASSENGER LOCK FILE ISSUES" +echo "==========================================" +echo "" + +# Navigate to app directory +cd ~/product-finder + +# Remove any stale lock files +echo "🧹 Cleaning stale lock files..." +if [ -d "tmp" ]; then + rm -rf tmp/* + echo " āœ“ tmp directory cleaned" +else + echo " āš ļø tmp directory doesn't exist - will create it" +fi + +# Create tmp directory with correct permissions +echo "" +echo "šŸ“ Setting up tmp directory..." +mkdir -p tmp +chmod 755 tmp +echo " āœ“ tmp directory created with 755 permissions" + +# Create restart file +echo "" +echo "šŸ”„ Restarting Passenger..." +touch tmp/restart.txt +chmod 644 tmp/restart.txt +echo " āœ“ tmp/restart.txt created" + +# Set correct permissions for app files +echo "" +echo "šŸ”’ Setting file permissions..." +chmod 755 . +chmod 644 passenger_wsgi.py +chmod 644 app.py +chmod 644 .htaccess +chmod -R 755 data 2>/dev/null || mkdir -p data && chmod 755 data +chmod -R 755 templates +chmod -R 755 static 2>/dev/null || true +echo " āœ“ File permissions set" + +# Show current tmp directory status +echo "" +echo "šŸ“Š Current tmp directory status:" +ls -la tmp/ +echo "" + +echo "==========================================" +echo "āœ“ LOCK FILE ISSUE FIXED" +echo "==========================================" +echo "" +echo "Next steps:" +echo "1. Wait 30 seconds for Passenger to restart" +echo "2. Test: https://columbiawindows.com/product-finder/" +echo "" +echo "If still failing, check error log:" +echo "tail -50 ~/logs/error_log" +echo "" diff --git a/app/generate_bitwise_helper.py b/app/generate_bitwise_helper.py new file mode 100644 index 0000000..015728b --- /dev/null +++ b/app/generate_bitwise_helper.py @@ -0,0 +1,295 @@ +""" +Generate Bitwise Helper File for Product Classification +Creates a helper file with bitwise flags based on product attributes (columns G-S) +""" + +import csv +import json +from collections import defaultdict + +# Bit position definitions +BIT_DEFINITIONS = { + # Base Type (Bits 0-1) + 'base_door': 0, # 2^0 = 1 + 'base_window': 1, # 2^1 = 2 + + # Product Flags (Bits 2-3) + 'is_accessory': 2, # 2^2 = 4 + 'specific_item': 3, # 2^3 = 8 + + # Materials (Bits 4-5) + 'material_aluminum': 4, # 2^4 = 16 + 'material_vinyl': 5, # 2^5 = 32 + + # Colors (Bits 6-11) + 'color_black': 6, # 2^6 = 64 + 'color_white': 7, # 2^7 = 128 + 'color_bronze': 8, # 2^8 = 256 + 'color_tan': 9, # 2^9 = 512 + 'color_mill': 10, # 2^10 = 1024 + 'color_sandstone': 11, # 2^11 = 2048 +} + +# Sub-type bits will be assigned dynamically (starting at bit 12) +SUBTYPE_BIT_START = 12 + +def parse_bool(value): + """Parse TRUE/FALSE string to boolean""" + if isinstance(value, str): + return value.strip().upper() == 'TRUE' + return bool(value) + +def calculate_bit_value(row, subtype_map): + """Calculate the bitwise value for a product row""" + bit_value = 0 + explanation = [] + + # Base Type (Column G - index 6) + base_type = row[6].strip() if len(row) > 6 and row[6] else "" + if base_type.lower() == 'door': + bit_value |= (1 << BIT_DEFINITIONS['base_door']) + explanation.append('Door') + elif base_type.lower() == 'window': + bit_value |= (1 << BIT_DEFINITIONS['base_window']) + explanation.append('Window') + + # Accessory Flag (Column J - index 9) + if len(row) > 9 and parse_bool(row[9]): + bit_value |= (1 << BIT_DEFINITIONS['is_accessory']) + explanation.append('Accessory') + + # Specific Item Link (Column K - index 10) + if len(row) > 10 and parse_bool(row[10]): + bit_value |= (1 << BIT_DEFINITIONS['specific_item']) + explanation.append('SpecificItem') + + # Materials + if len(row) > 11 and parse_bool(row[11]): # Aluminum (Column L) + bit_value |= (1 << BIT_DEFINITIONS['material_aluminum']) + explanation.append('Aluminum') + + if len(row) > 12 and parse_bool(row[12]): # Vinyl (Column M) + bit_value |= (1 << BIT_DEFINITIONS['material_vinyl']) + explanation.append('Vinyl') + + # Colors + color_columns = [ + (13, 'color_black', 'Black'), + (14, 'color_white', 'White'), + (15, 'color_bronze', 'Bronze'), + (16, 'color_tan', 'Tan'), + (17, 'color_mill', 'Mill'), + (18, 'color_sandstone', 'Sandstone'), + ] + + for col_idx, bit_key, color_name in color_columns: + if len(row) > col_idx and parse_bool(row[col_idx]): + bit_value |= (1 << BIT_DEFINITIONS[bit_key]) + explanation.append(color_name) + + # Sub-types (Columns H and I - indices 7 and 8) + door_subtype = row[7].strip() if len(row) > 7 and row[7] else "" + window_subtype = row[8].strip() if len(row) > 8 and row[8] else "" + + if door_subtype and door_subtype in subtype_map: + bit_pos = subtype_map[door_subtype] + bit_value |= (1 << bit_pos) + explanation.append(f'Subtype:{door_subtype}') + + if window_subtype and window_subtype in subtype_map: + bit_pos = subtype_map[window_subtype] + bit_value |= (1 << bit_pos) + explanation.append(f'Subtype:{window_subtype}') + + return bit_value, explanation + +def collect_subtypes(csv_path): + """Collect all unique sub-types from the CSV""" + subtypes = set() + + with open(csv_path, 'r', encoding='utf-8') as f: + reader = csv.reader(f) + next(reader) # Skip first header row + next(reader) # Skip second header row + + for row in reader: + if len(row) < 8: + continue + + # Skip discontinued items + if len(row) > 0 and parse_bool(row[0]): + continue + + # Collect door subtype (column H - index 7) + if len(row) > 7 and row[7].strip(): + subtypes.add(row[7].strip()) + + # Collect window subtype (column I - index 8) + if len(row) > 8 and row[8].strip(): + subtypes.add(row[8].strip()) + + return sorted(subtypes) + +def generate_helper_files(csv_path): + """Generate helper CSV and JSON files with bitwise values""" + + print("Analyzing product data...") + + # First pass: collect all unique sub-types + subtypes = collect_subtypes(csv_path) + + # Create subtype bit mapping + subtype_map = {} + for idx, subtype in enumerate(subtypes): + subtype_map[subtype] = SUBTYPE_BIT_START + idx + + print(f"Found {len(subtypes)} unique sub-types") + + # Second pass: calculate bit values + results = [] + + with open(csv_path, 'r', encoding='utf-8') as f: + reader = csv.reader(f) + next(reader) # Skip first header row + next(reader) # Skip second header row + + for row in reader: + if len(row) < 3: + continue + + prod_code = row[2].strip() # Column C - PROD_CODE + discontinued = parse_bool(row[0]) # Column A - DISCONT + + bit_value, explanation = calculate_bit_value(row, subtype_map) + + results.append({ + 'PROD_CODE': prod_code, + 'BIT_VALUE': bit_value, + 'BIT_HEX': f"0x{bit_value:X}", + 'BIT_BINARY': f"{bit_value:b}", + 'DISCONTINUED': discontinued, + 'FLAGS': ' | '.join(explanation) if explanation else 'None' + }) + + # Filter out discontinued products for output files + active_results = [r for r in results if not r['DISCONTINUED']] + + print(f"Total products processed: {len(results)}") + print(f"Active products: {len(active_results)}") + print(f"Discontinued products (filtered out): {len(results) - len(active_results)}") + + # Write CSV helper file (only active products) + csv_output = 'data/product_bitwise.csv' + with open(csv_output, 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=['PROD_CODE', 'BIT_VALUE', 'BIT_HEX', 'BIT_BINARY', 'DISCONTINUED', 'FLAGS']) + writer.writeheader() + writer.writerows(active_results) + + print(f"āœ“ Created {csv_output}") + + # Write JSON helper file (only active products) + json_output = 'data/product_bitwise.json' + with open(json_output, 'w', encoding='utf-8') as f: + json.dump(active_results, f, indent=2) + + print(f"āœ“ Created {json_output}") + + # Create bit legend/documentation + legend = { + 'description': 'Bitwise flag system for product classification', + 'bit_definitions': { + 'base_attributes': { + 'bit_0 (1)': 'Base Type = Door', + 'bit_1 (2)': 'Base Type = Window', + 'bit_2 (4)': 'Is Accessory', + 'bit_3 (8)': 'Specific Item Link', + }, + 'materials': { + 'bit_4 (16)': 'Material = Aluminum', + 'bit_5 (32)': 'Material = Vinyl', + }, + 'colors': { + 'bit_6 (64)': 'Color = Black', + 'bit_7 (128)': 'Color = White', + 'bit_8 (256)': 'Color = Bronze', + 'bit_9 (512)': 'Color = Tan', + 'bit_10 (1024)': 'Color = Mill', + 'bit_11 (2048)': 'Color = Sandstone', + }, + 'subtypes': {} + }, + 'usage_examples': { + 'check_if_door': 'bit_value & 1', + 'check_if_window': 'bit_value & 2', + 'check_if_aluminum': 'bit_value & 16', + 'check_if_white': 'bit_value & 128', + 'check_multiple': '(bit_value & 1) and (bit_value & 16) # Door AND Aluminum', + } + } + + # Add subtype mappings to legend + for subtype, bit_pos in sorted(subtype_map.items(), key=lambda x: x[1]): + bit_value = 1 << bit_pos + legend['bit_definitions']['subtypes'][f'bit_{bit_pos} ({bit_value})'] = f'Subtype = {subtype}' + legend['usage_examples'][f'check_subtype_{subtype.replace(" ", "_").lower()}'] = f'bit_value & {bit_value}' + + # Write legend file + legend_output = 'data/bitwise_legend.json' + with open(legend_output, 'w', encoding='utf-8') as f: + json.dump(legend, f, indent=2) + + print(f"āœ“ Created {legend_output}") + + # Generate statistics + stats = { + 'total_products': len([r for r in results if not r['DISCONTINUED']]), + 'total_discontinued': len([r for r in results if r['DISCONTINUED']]), + 'total_accessories': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 4)]), + 'doors': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 1)]), + 'windows': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 2)]), + 'aluminum_products': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 16)]), + 'vinyl_products': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 32)]), + 'multi_material': len([r for r in results if not r['DISCONTINUED'] and (r['BIT_VALUE'] & 16) and (r['BIT_VALUE'] & 32)]), + } + + print("\n" + "="*60) + print("STATISTICS") + print("="*60) + print(f"Total Active Products: {stats['total_products']}") + print(f"Total Discontinued: {stats['total_discontinued']}") + print(f"Accessories: {stats['total_accessories']}") + print(f"Doors: {stats['doors']}") + print(f"Windows: {stats['windows']}") + print(f"Aluminum Products: {stats['aluminum_products']}") + print(f"Vinyl Products: {stats['vinyl_products']}") + print(f"Multi-Material Products: {stats['multi_material']}") + print(f"Unique Sub-types: {len(subtypes)}") + print("="*60) + + # Show example products + print("\nEXAMPLE PRODUCTS:") + print("-"*60) + for i, result in enumerate(active_results[:5]): + print(f"{result['PROD_CODE']:12} | {result['BIT_VALUE']:6} | {result['BIT_HEX']:8} | {result['FLAGS']}") + print("-"*60) + + return active_results, legend, subtype_map + +if __name__ == '__main__': + csv_path = 'data/products.csv' + + try: + results, legend, subtype_map = generate_helper_files(csv_path) + print("\nāœ“ All helper files generated successfully!") + print("\nGenerated files:") + print(" - data/product_bitwise.csv (CSV format with bit values)") + print(" - data/product_bitwise.json (JSON format with bit values)") + print(" - data/bitwise_legend.json (Bit mapping documentation)") + + except FileNotFoundError: + print(f"Error: Could not find {csv_path}") + print("Make sure products.csv exists in the data/ folder") + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() diff --git a/app/image_generator.py b/app/image_generator.py new file mode 100644 index 0000000..82bdb3e --- /dev/null +++ b/app/image_generator.py @@ -0,0 +1,297 @@ +""" +Dynamic Product Image Generator +Generates product images with configurable colors, hardware positions, and other options. +Uses PIL/Pillow for image manipulation. +""" + +import os +import json +import hashlib +from io import BytesIO +from PIL import Image, ImageDraw, ImageEnhance, ImageColor +from pathlib import Path + + +class ProductImageGenerator: + """Handles dynamic generation of product images based on configuration.""" + + def __init__(self, config_file='data/image_configs.json'): + """Initialize with configuration file.""" + self.config_file = config_file + self.configs = self._load_configs() + self.cache_dir = Path('cache/product_images') + self.cache_dir.mkdir(parents=True, exist_ok=True) + + def _load_configs(self): + """Load image configuration from JSON file.""" + try: + with open(self.config_file, 'r') as f: + return json.load(f) + except Exception as e: + print(f"Error loading image configs: {e}") + return {} + + def _find_image(self, image_path): + """ + Find image file, trying different extensions if needed. + + Args: + image_path: Path to image (e.g., 'images/cobrai.jpg') + + Returns: + Actual path to image file, or None if not found + """ + # Try the exact path first + if os.path.exists(image_path): + return image_path + + # Get base path without extension + base_path = os.path.splitext(image_path)[0] + + # Try common image extensions + for ext in ['.png', '.jpg', '.jpeg', '.PNG', '.JPG', '.JPEG']: + test_path = base_path + ext + if os.path.exists(test_path): + return test_path + + return None + + def generate_product_image(self, product_code, color='white', hinge='right', + material='aluminum', use_cache=True): + """ + Generate a product image with specified configuration. + + Args: + product_code: Product identifier (e.g., 'cobrai') + color: Color name (e.g., 'white', 'black', 'bronze') + hinge: Hinge side ('right' or 'left') + material: Material type (for future use) + use_cache: Whether to use cached images + + Returns: + PIL Image object + """ + product_code = product_code.lower() + + # Check if product config exists + if product_code not in self.configs: + raise ValueError(f"Product {product_code} not found in configuration") + + config = self.configs[product_code] + + # Check cache first + if use_cache: + cache_key = self._get_cache_key(product_code, color, hinge, material) + cached_image = self._get_cached_image(cache_key) + if cached_image: + return cached_image + + # Load base image + base_image_path = config['sourceImage'] + + # Try to find the image with different extensions if needed + image_path = self._find_image(base_image_path) + + if not image_path: + raise FileNotFoundError(f"Could not load base image: {base_image_path}. Tried .jpg, .png, .jpeg") + + try: + base_image = Image.open(image_path).convert('RGBA') + except Exception as e: + raise FileNotFoundError(f"Could not load base image: {image_path}. Error: {e}") + + # Apply color to regions + if color in config['availableColors']: + color_rgb = tuple(config['availableColors'][color]['rgb']) + base_image = self._apply_color_to_regions( + base_image, + config['colorableRegions'], + color_rgb + ) + + # Apply hardware (handles, locks) + if hinge in config.get('hardwarePositions', {}): + base_image = self._apply_hardware( + base_image, + config['hardwarePositions'], + hinge + ) + + # Flip image if left hinge + if hinge == 'left': + base_image = base_image.transpose(Image.FLIP_LEFT_RIGHT) + + # Cache the result + if use_cache: + self._cache_image(cache_key, base_image) + + return base_image + + def _apply_color_to_regions(self, image, regions, target_color): + """ + Apply color to specific regions of the image. + + This method attempts to recolor the frame/panel areas while preserving + shadows, highlights, and texture. + """ + img_copy = image.copy() + pixels = img_copy.load() + width, height = img_copy.size + + # Get the average brightness of the target color + target_brightness = sum(target_color) / 3 + + for region in regions: + x1, y1 = region['topLeft'] + x2, y2 = region['bottomRight'] + + # Ensure coordinates are within bounds + x1 = max(0, min(x1, width - 1)) + x2 = max(0, min(x2, width)) + y1 = max(0, min(y1, height - 1)) + y2 = max(0, min(y2, height)) + + # Apply color to region while preserving luminosity + for y in range(y1, y2): + for x in range(x1, x2): + try: + r, g, b, a = pixels[x, y] + + # Calculate original brightness + original_brightness = (r + g + b) / 3 + + # Preserve relative brightness + if original_brightness > 0: + brightness_factor = original_brightness / 255.0 + + # Apply target color with brightness preservation + new_r = int(target_color[0] * brightness_factor) + new_g = int(target_color[1] * brightness_factor) + new_b = int(target_color[2] * brightness_factor) + + # Ensure values are in valid range + new_r = max(0, min(255, new_r)) + new_g = max(0, min(255, new_g)) + new_b = max(0, min(255, new_b)) + + pixels[x, y] = (new_r, new_g, new_b, a) + except IndexError: + continue + + return img_copy + + def _apply_hardware(self, image, hardware_positions, hinge_side): + """Apply hardware (handles, locks) to the image.""" + img_copy = image.copy() + + hardware_key = f"handle_{hinge_side}" + if hardware_key in hardware_positions: + hardware_config = hardware_positions[hardware_key] + hardware_path = hardware_config.get('image') + + if hardware_path: + # Try to find the hardware image with different extensions + actual_path = self._find_image(hardware_path) + + if actual_path: + try: + hardware_img = Image.open(actual_path).convert('RGBA') + + # Flip if needed + if hardware_config.get('flip', False): + hardware_img = hardware_img.transpose(Image.FLIP_LEFT_RIGHT) + + # Paste hardware at specified position + x, y = hardware_config['x'], hardware_config['y'] + img_copy.paste(hardware_img, (x, y), hardware_img) + except Exception as e: + print(f"Could not apply hardware: {e}") + + return img_copy + + def _get_cache_key(self, product_code, color, hinge, material): + """Generate a unique cache key for the configuration.""" + key_string = f"{product_code}_{color}_{hinge}_{material}" + return hashlib.md5(key_string.encode()).hexdigest() + + def _get_cached_image(self, cache_key): + """Retrieve cached image if available.""" + cache_path = self.cache_dir / f"{cache_key}.png" + if cache_path.exists(): + try: + return Image.open(cache_path) + except Exception: + return None + return None + + def _cache_image(self, cache_key, image): + """Save image to cache.""" + cache_path = self.cache_dir / f"{cache_key}.png" + try: + image.save(cache_path, 'PNG', optimize=True) + except Exception as e: + print(f"Could not cache image: {e}") + + def get_product_config(self, product_code): + """Get configuration for a specific product.""" + return self.configs.get(product_code.lower()) + + def clear_cache(self, product_code=None): + """Clear cached images. If product_code provided, only clear that product's cache.""" + if product_code: + # Clear specific product cache (would need to track cache keys) + pass + else: + # Clear all cache + for cache_file in self.cache_dir.glob('*.png'): + try: + cache_file.unlink() + except Exception: + pass + + +def recolor_image_advanced(image, source_color_range, target_color): + """ + Advanced recoloring that replaces pixels within a color range. + + Args: + image: PIL Image + source_color_range: Dict with 'min' and 'max' RGB tuples + target_color: Target RGB tuple + """ + img_copy = image.copy() + pixels = img_copy.load() + width, height = img_copy.size + + min_r, min_g, min_b = source_color_range['min'] + max_r, max_g, max_b = source_color_range['max'] + + for y in range(height): + for x in range(width): + r, g, b, a = pixels[x, y] + + # Check if pixel is within source color range + if (min_r <= r <= max_r and + min_g <= g <= max_g and + min_b <= b <= max_b): + + # Calculate brightness factor + brightness = (r + g + b) / 3 / 255.0 + + # Apply target color with brightness + new_r = int(target_color[0] * brightness) + new_g = int(target_color[1] * brightness) + new_b = int(target_color[2] * brightness) + + pixels[x, y] = (new_r, new_g, new_b, a) + + return img_copy + + +def image_to_base64(image, format='PNG'): + """Convert PIL Image to base64 string.""" + import base64 + buffered = BytesIO() + image.save(buffered, format=format) + img_str = base64.b64encode(buffered.getvalue()).decode() + return f"data:image/{format.lower()};base64,{img_str}" diff --git a/app/images/cobrai-black-black.png b/app/images/cobrai-black-black.png new file mode 100644 index 0000000..9f99647 Binary files /dev/null and b/app/images/cobrai-black-black.png differ diff --git a/app/images/cobrai-bronze-bronze.png b/app/images/cobrai-bronze-bronze.png new file mode 100644 index 0000000..e3f6456 Binary files /dev/null and b/app/images/cobrai-bronze-bronze.png differ diff --git a/app/images/cobrai-sandstone-bronze.png b/app/images/cobrai-sandstone-bronze.png new file mode 100644 index 0000000..2dd970a Binary files /dev/null and b/app/images/cobrai-sandstone-bronze.png differ diff --git a/app/images/cobrai-white-white.png b/app/images/cobrai-white-white.png new file mode 100644 index 0000000..9be2626 Binary files /dev/null and b/app/images/cobrai-white-white.png differ diff --git a/app/images/storm-door-background.png b/app/images/storm-door-background.png new file mode 100644 index 0000000..7b03e3d Binary files /dev/null and b/app/images/storm-door-background.png differ diff --git a/app/js/encoding_helper.js b/app/js/encoding_helper.js new file mode 100644 index 0000000..bcb93ed --- /dev/null +++ b/app/js/encoding_helper.js @@ -0,0 +1,451 @@ +/** + * Product Encoding/Decoding Helper + * + * Provides functions to encode and decode product configurations into compact + * hex-based URL strings. Uses bit-packing for efficient, stable encoding. + * + * Format: v-T-C-M-HHHH-HHHH-HHHH + * See /planning/ENCODING_SYSTEM.md for detailed documentation + */ + +// ============================================================================ +// LOOKUP TABLES +// ============================================================================ + +const ENCODING_VERSION = 0; + +// Main Product Attributes (single hex character, 0-15) +const PRODUCT_TYPE = { + 'PATIO_DOOR': 1, + 'STORM_DOOR': 3, + 'STORM_WINDOW': 5, + 'PRIMARY_WINDOW': 7 +}; + +const PRODUCT_TYPE_REVERSE = { + 1: 'PATIO_DOOR', + 3: 'STORM_DOOR', + 5: 'STORM_WINDOW', + 7: 'PRIMARY_WINDOW' +}; + +const PRODUCT_COLOR = { + 'BLACK': 1, + 'BRONZE': 2, + 'SANDSTONE': 3, + 'WHITE': 4, + 'TAN': 5, + 'MILL': 6 +}; + +const PRODUCT_COLOR_REVERSE = { + 1: 'BLACK', + 2: 'BRONZE', + 3: 'SANDSTONE', + 4: 'WHITE', + 5: 'TAN', + 6: 'MILL' +}; + +const PRODUCT_MATERIAL = { + 'ALUMINUM': 1, + 'VINYL': 2 +}; + +const PRODUCT_MATERIAL_REVERSE = { + 1: 'ALUMINUM', + 2: 'VINYL' +}; + +// Hardware Fields (5 bits each, 0-31) +const HARDWARE_TYPE = { + 'NONE': 0, + 'LEVER': 1, + 'PULL': 2, + 'PULL_HANDLE': 3, + 'DEADBOLT': 4, + 'HINGE': 5 +}; + +const HARDWARE_TYPE_REVERSE = { + 0: 'NONE', + 1: 'LEVER', + 2: 'PULL', + 3: 'PULL_HANDLE', + 4: 'DEADBOLT', + 5: 'HINGE' +}; + +const HARDWARE_STYLE = { + 'NONE': 0, + 'STANDARD': 1, + 'PUSH': 2, + 'ALTERNATIVE': 3, + 'CONTEMPORARY': 4, + 'TRADITIONAL': 5 +}; + +const HARDWARE_STYLE_REVERSE = { + 0: 'NONE', + 1: 'STANDARD', + 2: 'PUSH', + 3: 'ALTERNATIVE', + 4: 'CONTEMPORARY', + 5: 'TRADITIONAL' +}; + +const HARDWARE_COLOR = { + 'NONE': 0, + 'BRASS': 1, + 'WHITE': 2, + 'BLACK': 3, + 'SATIN': 4, + 'NICKEL': 5, + 'BRONZE': 6 +}; + +const HARDWARE_COLOR_REVERSE = { + 0: 'NONE', + 1: 'BRASS', + 2: 'WHITE', + 3: 'BLACK', + 4: 'SATIN', + 5: 'NICKEL', + 6: 'BRONZE' +}; + +// ============================================================================ +// ENCODING FUNCTIONS +// ============================================================================ + +/** + * Encode a hardware configuration into a 4-character hex string + * Uses 5 bits per field (32 values each) + * + * @param {number} type - Hardware type (0-31) + * @param {number} style - Hardware style (0-31) + * @param {number} color - Hardware color (0-31) + * @returns {string} 4-character hex string (e.g., "0421") + */ +function encodeHardware(type, style, color) { + // Validate inputs + if (type < 0 || type > 31) throw new Error(`Invalid hardware type: ${type} (must be 0-31)`); + if (style < 0 || style > 31) throw new Error(`Invalid hardware style: ${style} (must be 0-31)`); + if (color < 0 || color > 31) throw new Error(`Invalid hardware color: ${color} (must be 0-31)`); + + // Pack into 16 bits: [reserved(1)][type(5)][style(5)][color(5)] + const value = ((type & 0x1F) << 10) | // Bits 10-14 + ((style & 0x1F) << 5) | // Bits 5-9 + (color & 0x1F); // Bits 0-4 + + return value.toString(16).toUpperCase().padStart(4, '0'); +} + +/** + * Encode a complete product configuration into a URL string + * + * @param {Object} config - Product configuration + * @param {number} config.type - Product type (0-15) + * @param {number} config.color - Product color (0-15) + * @param {number} config.material - Product material (0-15) + * @param {Array} config.hardware - Array of hardware items (optional) + * @param {number} config.hardware[].type - Hardware type (0-31) + * @param {number} config.hardware[].style - Hardware style (0-31) + * @param {number} config.hardware[].color - Hardware color (0-31) + * @returns {string} Encoded URL string (e.g., "0-1-4-1-0421") + */ +function encodeProduct(config) { + // Validate main attributes + if (config.type < 0 || config.type > 15) { + throw new Error(`Invalid product type: ${config.type} (must be 0-15)`); + } + if (config.color < 0 || config.color > 15) { + throw new Error(`Invalid product color: ${config.color} (must be 0-15)`); + } + if (config.material < 0 || config.material > 15) { + throw new Error(`Invalid product material: ${config.material} (must be 0-15)`); + } + + // Build base string: version-type-color-material + const parts = [ + ENCODING_VERSION.toString(16).toUpperCase(), + config.type.toString(16).toUpperCase(), + config.color.toString(16).toUpperCase(), + config.material.toString(16).toUpperCase() + ]; + + // Add hardware items if present + if (config.hardware && config.hardware.length > 0) { + config.hardware.forEach(hw => { + parts.push(encodeHardware(hw.type, hw.style, hw.color)); + }); + } + + return parts.join('-'); +} + +/** + * Encode using named constants (convenience function) + * + * @param {Object} config - Product configuration with named values + * @param {string} config.type - Product type name (e.g., 'PATIO_DOOR') + * @param {string} config.color - Color name (e.g., 'WHITE') + * @param {string} config.material - Material name (e.g., 'ALUMINUM') + * @param {Array} config.hardware - Array of hardware items (optional) + * @returns {string} Encoded URL string + */ +function encodeProductByName(config) { + const numericConfig = { + type: PRODUCT_TYPE[config.type], + color: PRODUCT_COLOR[config.color], + material: PRODUCT_MATERIAL[config.material], + hardware: [] + }; + + if (config.hardware && config.hardware.length > 0) { + numericConfig.hardware = config.hardware.map(hw => ({ + type: HARDWARE_TYPE[hw.type], + style: HARDWARE_STYLE[hw.style], + color: HARDWARE_COLOR[hw.color] + })); + } + + return encodeProduct(numericConfig); +} + +// ============================================================================ +// DECODING FUNCTIONS +// ============================================================================ + +/** + * Decode a hardware hex string back to its components + * + * @param {string} hex - 4-character hex string (e.g., "0421") + * @returns {Object} Decoded hardware configuration + */ +function decodeHardware(hex) { + if (typeof hex !== 'string' || hex.length !== 4) { + throw new Error(`Invalid hardware hex string: ${hex} (must be 4 characters)`); + } + + const value = parseInt(hex, 16); + + if (isNaN(value)) { + throw new Error(`Invalid hex value: ${hex}`); + } + + return { + type: (value >> 10) & 0x1F, // Extract bits 10-14 + style: (value >> 5) & 0x1F, // Extract bits 5-9 + color: value & 0x1F // Extract bits 0-4 + }; +} + +/** + * Decode a complete product URL string + * + * @param {string} encodedString - Encoded URL string (e.g., "0-1-4-1-0421") + * @returns {Object} Decoded product configuration + */ +function decodeProduct(encodedString) { + if (typeof encodedString !== 'string') { + throw new Error('Invalid encoded string: must be a string'); + } + + const parts = encodedString.split('-'); + + if (parts.length < 4) { + throw new Error(`Invalid encoded string: ${encodedString} (must have at least 4 parts)`); + } + + // Parse version + const version = parseInt(parts[0], 16); + if (version !== ENCODING_VERSION) { + throw new Error(`Unsupported encoding version: ${version} (current: ${ENCODING_VERSION})`); + } + + // Parse main attributes + const config = { + version: version, + type: parseInt(parts[1], 16), + color: parseInt(parts[2], 16), + material: parseInt(parts[3], 16), + hardware: [] + }; + + // Parse hardware items (if present) + for (let i = 4; i < parts.length; i++) { + config.hardware.push(decodeHardware(parts[i])); + } + + return config; +} + +/** + * Decode and return named values (convenience function) + * + * @param {string} encodedString - Encoded URL string + * @returns {Object} Decoded product with named values + */ +function decodeProductToNames(encodedString) { + const numeric = decodeProduct(encodedString); + + return { + version: numeric.version, + type: PRODUCT_TYPE_REVERSE[numeric.type] || `UNKNOWN_${numeric.type}`, + color: PRODUCT_COLOR_REVERSE[numeric.color] || `UNKNOWN_${numeric.color}`, + material: PRODUCT_MATERIAL_REVERSE[numeric.material] || `UNKNOWN_${numeric.material}`, + hardware: numeric.hardware.map(hw => ({ + type: HARDWARE_TYPE_REVERSE[hw.type] || `UNKNOWN_${hw.type}`, + style: HARDWARE_STYLE_REVERSE[hw.style] || `UNKNOWN_${hw.style}`, + color: HARDWARE_COLOR_REVERSE[hw.color] || `UNKNOWN_${hw.color}` + })) + }; +} + +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +/** + * Get the binary representation of a hex string (for debugging) + * + * @param {string} hex - Hex string + * @returns {string} Binary string with spaces every 4 bits + */ +function hexToBinary(hex) { + const value = parseInt(hex, 16); + const binary = value.toString(2).padStart(16, '0'); + return binary.match(/.{1,4}/g).join(' '); +} + +/** + * Validate that a configuration uses only defined values + * + * @param {Object} config - Product configuration + * @returns {Object} Validation result with errors array + */ +function validateConfiguration(config) { + const errors = []; + + if (!PRODUCT_TYPE_REVERSE[config.type]) { + errors.push(`Invalid product type: ${config.type}`); + } + if (!PRODUCT_COLOR_REVERSE[config.color]) { + errors.push(`Invalid product color: ${config.color}`); + } + if (!PRODUCT_MATERIAL_REVERSE[config.material]) { + errors.push(`Invalid product material: ${config.material}`); + } + + if (config.hardware) { + config.hardware.forEach((hw, index) => { + if (!HARDWARE_TYPE_REVERSE[hw.type]) { + errors.push(`Invalid hardware ${index + 1} type: ${hw.type}`); + } + if (!HARDWARE_STYLE_REVERSE[hw.style]) { + errors.push(`Invalid hardware ${index + 1} style: ${hw.style}`); + } + if (!HARDWARE_COLOR_REVERSE[hw.color]) { + errors.push(`Invalid hardware ${index + 1} color: ${hw.color}`); + } + }); + } + + return { + valid: errors.length === 0, + errors: errors + }; +} + +// ============================================================================ +// EXPORTS (for Node.js / Module usage) +// ============================================================================ + +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + // Constants + ENCODING_VERSION, + PRODUCT_TYPE, + PRODUCT_COLOR, + PRODUCT_MATERIAL, + HARDWARE_TYPE, + HARDWARE_STYLE, + HARDWARE_COLOR, + + // Encoding functions + encodeHardware, + encodeProduct, + encodeProductByName, + + // Decoding functions + decodeHardware, + decodeProduct, + decodeProductToNames, + + // Utilities + hexToBinary, + validateConfiguration + }; +} + +// ============================================================================ +// EXAMPLE USAGE +// ============================================================================ + +/* +// Example 1: Encode by numeric values +const encoded1 = encodeProduct({ + type: 1, // Patio Door + color: 4, // White + material: 1, // Aluminum + hardware: [ + { type: 1, style: 1, color: 1 } // Lever, Standard, Brass + ] +}); +console.log(encoded1); // "0-1-4-1-0421" + +// Example 2: Encode by named values +const encoded2 = encodeProductByName({ + type: 'STORM_DOOR', + color: 'WHITE', + material: 'ALUMINUM', + hardware: [ + { type: 'LEVER', style: 'STANDARD', color: 'BRASS' }, + { type: 'PULL_HANDLE', style: 'ALTERNATIVE', color: 'SATIN' } + ] +}); +console.log(encoded2); // "0-3-4-1-0421-0C64" + +// Example 3: Decode back to numeric values +const decoded1 = decodeProduct("0-1-4-1-0421"); +console.log(decoded1); +// { version: 0, type: 1, color: 4, material: 1, hardware: [{ type: 1, style: 1, color: 1 }] } + +// Example 4: Decode to named values +const decoded2 = decodeProductToNames("0-3-4-1-0421-0C64"); +console.log(decoded2); +// { +// version: 0, +// type: 'STORM_DOOR', +// color: 'WHITE', +// material: 'ALUMINUM', +// hardware: [ +// { type: 'LEVER', style: 'STANDARD', color: 'BRASS' }, +// { type: 'PULL_HANDLE', style: 'ALTERNATIVE', color: 'SATIN' } +// ] +// } + +// Example 5: Debug hardware encoding +const hwHex = encodeHardware(3, 12, 20); +console.log(hwHex); // "0D94" +console.log(hexToBinary(hwHex)); // "0000 1101 1001 0100" +console.log(decodeHardware(hwHex)); // { type: 3, style: 12, color: 20 } + +// Example 6: Validate configuration +const validation = validateConfiguration({ + type: 1, color: 4, material: 1, + hardware: [{ type: 1, style: 1, color: 1 }] +}); +console.log(validation); // { valid: true, errors: [] } +*/ diff --git a/app/js/script.js b/app/js/script.js new file mode 100644 index 0000000..334c6d7 --- /dev/null +++ b/app/js/script.js @@ -0,0 +1,1444 @@ +// Question and answer data structure +// Loaded from JSON file via AJAX + +// Store all user answers +const userAnswers = {}; + +// Question data loaded from JSON +let questionData = {}; +let productData = []; +let accessoryData = []; +let bitwiseData = {}; + +// Track accumulated bit value for URL state +let accumulatedBitValue = 0; + +// Bit definitions (must match generate_bitwise_helper.py) +const BIT_DEFINITIONS = { + 'base_door': 0, // 1 + 'base_window': 1, // 2 + 'is_accessory': 2, // 4 + 'specific_item': 3, // 8 + 'material_aluminum': 4, // 16 + 'material_vinyl': 5, // 32 + 'color_black': 6, // 64 + 'color_white': 7, // 128 + 'color_bronze': 8, // 256 + 'color_tan': 9, // 512 + 'color_mill': 10, // 1024 + 'color_sandstone': 11, // 2048 + 'subtype_patio_door': 12, // 4096 + 'subtype_primary_window': 13, // 8192 + 'subtype_storm_door': 14, // 16384 + 'subtype_storm_window': 15 // 32768 +}; + +// Initialize the quiz +function init() { + // Load question data from JSON file + loadQuestionData(); +} + +// Load question data and other JSON files +function loadQuestionData() { + // Load all data files in parallel + Promise.all([ + fetch('data/navigation.json').then(r => r.ok ? r.json() : Promise.reject('navigation.json not found')), + fetch('data/products.json').then(r => r.ok ? r.json() : []).catch(() => []), + fetch('data/accessories.json').then(r => r.ok ? r.json() : []).catch(() => []), + fetch('data/product_bitwise.json').then(r => r.ok ? r.json() : []).catch(() => []) + ]) + .then(([questions, products, accessories, bitwise]) => { + questionData = questions; + productData = products; + accessoryData = accessories; + + // Index bitwise data by product code + bitwiseData = {}; + bitwise.forEach(item => { + bitwiseData[item.PROD_CODE] = item.BIT_VALUE; + }); + + console.log('Loaded:', { + questions: Object.keys(questionData).length, + products: productData.length, + accessories: accessoryData.length, + bitwise: Object.keys(bitwiseData).length + }); + + // Check if there's state in URL + initFromURL(); + }) + .catch(error => { + console.error('Error loading data:', error); + document.getElementById('content').innerHTML = ` +
+
Error Loading Application
+
+ Failed to load data files. Please refresh the page or contact support.
+ Error: ${error} +
+
+ `; + }); +} + +// Handle conditional logic for q-material +function getNextForMaterial(answers) { + const productType = answers['start']; + if (productType === 'Window') { + return 'q-window-type'; + } else if (productType === 'Door') { + return 'q-door-type'; + } else if (productType === 'Patio Door') { + return 'q-patio'; + } + return 'q-window-type'; // default +} + +// Navigation history +let history = ['start']; + +// Initialize from URL parameters +function initFromURL() { + const params = new URLSearchParams(window.location.search); + + // Priority 1: Direct product view + const productCode = params.get('p'); + if (productCode) { + accumulatedBitValue = parseInt(params.get('b') || '0', 10); + showProductByCode(productCode); + return; + } + + // Priority 2: Restore navigation state + const bitValue = params.get('b'); + const questionKey = params.get('q'); + + if (bitValue) { + accumulatedBitValue = parseInt(bitValue, 10); + + if (questionKey) { + // Special case: results page + if (questionKey === 'results') { + restoreStateFromBitValue(accumulatedBitValue, null); + history.push('results'); + showFilteredProducts(); + return; + } + + restoreStateFromBitValue(accumulatedBitValue, questionKey); + return; + } + } + + // Priority 3: Start fresh + loadContent('start'); +} + +// Restore state from bit value +function restoreStateFromBitValue(bitValue, questionKey) { + // Decode bit value back to user selections + + // Base type + if (bitValue & 1) { + userAnswers['start'] = 'Door'; + } else if (bitValue & 2) { + userAnswers['start'] = 'Window'; + } + + // Subtypes + if (bitValue & (1 << BIT_DEFINITIONS['subtype_patio_door'])) { + if (bitValue & 1) userAnswers['q-door-type'] = 'Patio Door'; + } + if (bitValue & (1 << BIT_DEFINITIONS['subtype_storm_door'])) { + if (bitValue & 1) userAnswers['q-door-type'] = 'Storm Door'; + } + if (bitValue & (1 << BIT_DEFINITIONS['subtype_primary_window'])) { + if (bitValue & 2) userAnswers['q-window-type'] = 'Primary Window'; + } + if (bitValue & (1 << BIT_DEFINITIONS['subtype_storm_window'])) { + if (bitValue & 2) userAnswers['q-window-type'] = 'Storm Window'; + } + + // Materials + if (bitValue & 16) { + userAnswers['material'] = 'Aluminum'; + } else if (bitValue & 32) { + userAnswers['material'] = 'Vinyl'; + } + + // Colors + const colorMap = { + 64: 'Black', 128: 'White', 256: 'Bronze', + 512: 'Tan', 1024: 'Mill', 2048: 'Sandstone' + }; + + for (const [bit, color] of Object.entries(colorMap)) { + if (bitValue & parseInt(bit)) { + userAnswers['color'] = color; + break; + } + } + + // Navigate to the saved question + history = ['start']; + if (questionKey && questionKey !== 'start') { + history.push(questionKey); + } + loadContent(questionKey || 'start'); +} + +// Update URL with current state +function updateURL(questionKey = null) { + const params = new URLSearchParams(); + + if (accumulatedBitValue > 0) { + params.set('b', accumulatedBitValue.toString()); + } + + const currentKey = questionKey || (history.length > 0 ? history[history.length - 1] : 'start'); + if (currentKey && currentKey !== 'start') { + params.set('q', currentKey); + } + + const newURL = window.location.pathname + (params.toString() ? '?' + params.toString() : ''); + window.history.replaceState( + { bitValue: accumulatedBitValue, questionKey: currentKey }, + '', + newURL + ); +} + +// Update bit value based on selection +function updateBitValue(answerObject) { + if (!answerObject || !answerObject.filter) return; + + const filter = answerObject.filter; + + // Base type + if (filter.baseType === 'Door') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['base_door']); + } else if (filter.baseType === 'Window') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['base_window']); + } + + // Materials + if (filter.material === 'Aluminum') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['material_aluminum']); + } else if (filter.material === 'Vinyl') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['material_vinyl']); + } + + // Colors + const colorKey = filter.color ? 'color_' + filter.color.toLowerCase() : null; + if (colorKey && BIT_DEFINITIONS[colorKey]) { + accumulatedBitValue |= (1 << BIT_DEFINITIONS[colorKey]); + } + + // Subtypes + if (filter.subType) { + const subtypeKey = 'subtype_' + filter.subType.toLowerCase().replace(/ /g, '_'); + if (BIT_DEFINITIONS[subtypeKey]) { + accumulatedBitValue |= (1 << BIT_DEFINITIONS[subtypeKey]); + } + } +} + +// Load content based on key +function loadContent(key) { + // Special handling for results + if (key === 'results') { + showFilteredProducts(); + return; + } + + const data = questionData[key]; + if (!data) { + console.error('Content not found for key:', key); + return; + } + + const contentDiv = document.getElementById('content'); + + if (data.type === 'question') { + if (data.inputType === 'form') { + renderForm(data, key); + } else { + renderQuestion(data, key); + } + } else if (data.type === 'product') { + renderProduct(data); + } + + updateBreadcrumb(); +} + +// Helper function to render notes section +function renderNotesSection(notes) { + const notesId = 'notes-' + Date.now(); + return ` +
+ + +
+ `; +} + +// Toggle notes visibility +function toggleNotes(notesId) { + const notesContent = document.getElementById(notesId); + const arrow = document.getElementById(notesId + '-arrow'); + + if (notesContent.style.display === 'none') { + notesContent.style.display = 'block'; + arrow.textContent = 'ā–²'; + } else { + notesContent.style.display = 'none'; + arrow.textContent = 'ā–¼'; + } +} + +// Render a question with answer buttons +function renderQuestion(data, currentKey) { + const contentDiv = document.getElementById('content'); + + let html = ` +
+
${data.title}
+
${data.subtitle}
+
+ `; + + html += ` +
+
+ `; + + data.answers.forEach((answer, index) => { + html += ` + + `; + }); + + html += ` +
+
+ `; + + // Add notes section if available + if (data.notes) { + html += renderNotesSection(data.notes); + } + + contentDiv.innerHTML = html; +} + +// Render a form with text inputs +function renderForm(data, currentKey) { + const contentDiv = document.getElementById('content'); + + let html = ` +
+
${data.title}
+
${data.subtitle}
+
+ `; + + // Render measurement type toggle buttons if configuration exists + if (data.measurementType && data.measurementType.options) { + const currentMeasurementType = userAnswers[`${currentKey}.measurementType`] || data.measurementType.defaultValue; + + html += `
`; + data.measurementType.options.forEach(option => { + const isSelected = currentMeasurementType === option.value; + const disabledClass = option.disabled ? 'disabled' : ''; + const disabledAttr = option.disabled ? 'disabled' : ''; + + html += ` + + `; + }); + html += `
`; + } + + html += ` +
+
+ `; + + data.fields.forEach((field, index) => { + html += `
`; + html += ``; + + if (field.type === 'select') { + html += ``; + } else if (field.type === 'textarea') { + const value = userAnswers[`${currentKey}.${field.name}`] || ''; + html += ``; + } else { + const value = userAnswers[`${currentKey}.${field.name}`] || ''; + html += ``; + } + + html += `
`; + }); + + html += ` +
+ +
+
+
+ `; + + // Add notes section if available + if (data.notes) { + html += renderNotesSection(data.notes); + } + + contentDiv.innerHTML = html; +} + +// Render a product result +function renderProduct(data) { + const contentDiv = document.getElementById('content'); + + // Build specifications from stored answers + let specsHtml = ''; + if (Object.keys(userAnswers).length > 0) { + specsHtml = 'Your Specifications:
'; + for (const [key, value] of Object.entries(userAnswers)) { + if (value && key !== 'start') { + const displayKey = key.split('.').pop().replace(/([A-Z])/g, ' $1').trim(); + specsHtml += `${displayKey.charAt(0).toUpperCase() + displayKey.slice(1)}: ${value}
`; + } + } + specsHtml += '
'; + } + + // Get product image (use placeholder if not specified) + const productImage = data.image || 'https://via.placeholder.com/400x400/e0e0e0/666?text=' + encodeURIComponent(data.code); + + let html = ` +
+
${data.title}
+
+
+ ${data.title} +
+
+ Product Code: ${data.code}
+ Category: ${data.category}

+ ${specsHtml} + ${data.description}

+ Key Features: +
    + ${data.features.map(f => `
  • ${f}
  • `).join('')} +
+
+
+
+ + +
+
+ `; + + contentDiv.innerHTML = html; +} + +// Handle answer selection from buttons +function handleAnswer(currentKey, answerValue, nextKeyOrIndex) { + // Store the answer + userAnswers[currentKey] = answerValue; + + // Check if this is an index (new format) or key (old format) + let nextKey = nextKeyOrIndex; + const currentQuestion = questionData[currentKey]; + + if (currentQuestion && currentQuestion.answers && typeof nextKeyOrIndex === 'number') { + // New format: index provided + const answerObject = currentQuestion.answers[nextKeyOrIndex]; + nextKey = answerObject.next; + + // Store dimensions if provided (for preset sizes) + if (answerObject.dimensions) { + userAnswers['q-dimensions.width'] = answerObject.dimensions.width; + userAnswers['q-dimensions.height'] = answerObject.dimensions.height; + } + + // Update bit value if filter exists + if (answerObject.filter) { + updateBitValue(answerObject); + } + } + + history.push(nextKey); + updateURL(nextKey); + loadContent(nextKey); +} + +// Handle answer by index only (used by buttons to avoid escaping issues) +function handleAnswerByIndex(currentKey, answerIndex) { + const currentQuestion = questionData[currentKey]; + if (!currentQuestion || !currentQuestion.answers) { + console.error('Invalid question:', currentKey); + return; + } + + const answerObject = currentQuestion.answers[answerIndex]; + if (!answerObject) { + console.error('Invalid answer index:', answerIndex); + return; + } + + // Store the answer caption + userAnswers[currentKey] = answerObject.caption; + + // Store dimensions if provided (for preset sizes) + if (answerObject.dimensions) { + userAnswers['q-dimensions.width'] = answerObject.dimensions.width; + userAnswers['q-dimensions.height'] = answerObject.dimensions.height; + } + + // Update bit value if filter exists + if (answerObject.filter) { + updateBitValue(answerObject); + } + + // Navigate to next question + history.push(answerObject.next); + updateURL(answerObject.next); + loadContent(answerObject.next); +} + +// Handle measurement type toggle button click +function handleMeasurementTypeToggle(value, currentKey) { + // Store the selected measurement type + userAnswers[`${currentKey}.measurementType`] = value; + + // Update the UI to reflect the selection + const toggleButtons = document.querySelectorAll('.measurement-toggle'); + toggleButtons.forEach(button => { + if (button.dataset.value === value) { + button.classList.add('selected'); + } else { + button.classList.remove('selected'); + } + }); +} + +// Handle form submission +function handleFormSubmit(event, currentKey) { + event.preventDefault(); + + const form = event.target; + const formData = new FormData(form); + + // Store all form field values + for (const [key, value] of formData.entries()) { + userAnswers[`${currentKey}.${key}`] = value; + } + + // Ensure measurement type is saved if it was selected + const data = questionData[currentKey]; + if (data.measurementType && !userAnswers[`${currentKey}.measurementType`]) { + userAnswers[`${currentKey}.measurementType`] = data.measurementType.defaultValue; + } + + // Get the next key (handle conditional logic) + let nextKey; + + // Check if this is the q-material question that needs conditional logic + if (currentKey === 'q-material' && data.next === 'q-material-conditional') { + nextKey = getNextForMaterial(userAnswers); + } else { + nextKey = data.next; + } + + // Special handling for results + if (nextKey === 'results') { + history.push(nextKey); + updateURL(nextKey); + showFilteredProducts(); + return; + } + + history.push(nextKey); + updateURL(nextKey); + loadContent(nextKey); +} + +// Go back to previous question +function goBack() { + if (history.length > 1) { + history.pop(); + const previousKey = history[history.length - 1]; + loadContent(previousKey); + } +} + +// Start over +function startOver() { + history = ['start']; + // Clear all stored answers + for (const key in userAnswers) { + delete userAnswers[key]; + } + // Clear bit value + accumulatedBitValue = 0; + // Clear URL + window.history.replaceState({}, '', window.location.pathname); + loadContent('start'); +} + +// Show product by code (from URL) +function showProductByCode(productCode) { + if (productData.length === 0) { + document.getElementById('content').innerHTML = ` +
+
Products Not Loaded
+
+ Product data not available yet. Please generate JSON files first. +
+ +
+ `; + return; + } + + const product = productData.find(p => p.productCode === productCode || p.id === productCode); + if (!product) { + document.getElementById('content').innerHTML = ` +
+
Product Not Found
+
+ Product ${productCode} not found. +
+ +
+ `; + return; + } + + showProductDetail(product); +} + +// Show product detail +function showProductDetail(product) { + const prodCode = product.productCode || product.id; + + // Add to history if not already there + const productKey = `product-${prodCode}`; + if (history[history.length - 1] !== productKey) { + history.push(productKey); + } + + // Update URL with product code + const params = new URLSearchParams(); + params.set('p', prodCode); + if (accumulatedBitValue > 0) { + params.set('b', accumulatedBitValue.toString()); + } + + window.history.pushState( + { productCode: prodCode }, + '', + '?' + params.toString() + ); + + // Get compatible accessories if available + const compatibleAccessories = accessoryData.filter(acc => + product.compatibleAccessories && product.compatibleAccessories.includes(acc.id) + ); + + // Build specifications from stored answers + let specsHtml = ''; + if (Object.keys(userAnswers).length > 0) { + specsHtml = 'Your Specifications:
'; + for (const [key, value] of Object.entries(userAnswers)) { + if (value && key !== 'start') { + const displayKey = key.split('.').pop().replace(/([A-Z])/g, ' $1').trim(); + specsHtml += `${displayKey.charAt(0).toUpperCase() + displayKey.slice(1)}: ${value}
`; + } + } + specsHtml += '
'; + } + + // Get product details + const title = product.title || product.description || product.DESCRIPTION || prodCode; + const category = product.category || product.CATEGORY || 'N/A'; + const description = product.detailedDescription || product.description || product.DESCRIPTION || 'No description available'; + const features = product.features || []; + + // Get product image configuration + const productImage = product.image || `https://via.placeholder.com/400x400/e0e0e0/666?text=${encodeURIComponent(prodCode)}`; + const hasLayeredImages = product.imageConfig && product.imageConfig.layered === true; + const hasFlatImages = product.flatImages === true || product.imagePattern; + const useDynamicAPI = product.useDynamicAPI === true; + + // Get available materials and colors from product data + const availableMaterials = product.materials || []; + const availableColors = product.colors || []; + const availableSizes = product.sizes || []; + + // Sort materials alphabetically + const sortedMaterials = [...availableMaterials].sort((a, b) => a.localeCompare(b)); + + // Sort colors alphabetically, but put White at the bottom + const sortedColors = [...availableColors].sort((a, b) => { + if (a === 'White') return 1; + if (b === 'White') return -1; + return a.localeCompare(b); + }); + + // Get user's quiz answers to pre-select dropdowns + // Check multiple possible keys for material and color + let selectedMaterial = null; + let selectedColor = null; + + for (const key in userAnswers) { + if (key.includes('material') && userAnswers[key]) { + selectedMaterial = userAnswers[key].toLowerCase(); + } + if (key.includes('color') && userAnswers[key]) { + selectedColor = userAnswers[key].toLowerCase(); + } + } + + // Build material options + let materialOptionsHtml = ''; + if (sortedMaterials.length === 0) { + materialOptionsHtml = ''; + } else if (sortedMaterials.length === 1) { + materialOptionsHtml = ``; + } else { + materialOptionsHtml = ''; + sortedMaterials.forEach(material => { + const materialLower = material.toLowerCase(); + const selected = (selectedMaterial && materialLower === selectedMaterial) ? 'selected' : ''; + materialOptionsHtml += ``; + }); + } + + // Build color options + let colorOptionsHtml = ''; + if (sortedColors.length === 0) { + colorOptionsHtml = ''; + } else if (sortedColors.length === 1) { + colorOptionsHtml = ``; + } else { + colorOptionsHtml = ''; + sortedColors.forEach(color => { + const colorLower = color.toLowerCase(); + const selected = (selectedColor && colorLower === selectedColor) ? 'selected' : ''; + colorOptionsHtml += ``; + }); + } + + // Get dimensions from quiz to pre-select size + const quizWidth = userAnswers['q-dimensions.width']; + const quizHeight = userAnswers['q-dimensions.height']; + const selectedSize = (quizWidth && quizHeight) ? `${quizWidth}x${quizHeight}` : null; + + // Build size options + let sizeOptionsHtml = ''; + if (availableSizes.length > 0) { + availableSizes.forEach(size => { + if (typeof size === 'object' && size.value && size.label) { + const selected = (selectedSize && size.value === selectedSize) ? 'selected' : ''; + sizeOptionsHtml += ``; + } else if (typeof size === 'string') { + const selected = (selectedSize && size === selectedSize) ? 'selected' : ''; + sizeOptionsHtml += ``; + } + }); + } else { + // Default generic sizes if none specified + sizeOptionsHtml += ``; + sizeOptionsHtml += ``; + sizeOptionsHtml += ``; + } + sizeOptionsHtml += ''; + + const contentDiv = document.getElementById('content'); + + // Build the image display (layered, flat, or dynamic API) + let imageDisplayHtml = ''; + + if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) { + // Use layered image system + const basePath = product.imageConfig.basePath || ''; + const layers = product.imageConfig.layers || {}; + + imageDisplayHtml = ` +
+ ${layers.base ? `Base` : ''} + + ${layers.hardware ? `Hardware` : ''} + + +
+ `; + } else { + // Use flat image (for pre-rendered images or dynamic API) + // Determine initial image based on user's quiz answer, or default/first color + let initialImage = productImage; + + if (hasFlatImages && sortedColors.length > 0) { + // Priority: 1) User's quiz selection, 2) Single color auto-select, 3) Default to 'white' + let defaultColor; + if (selectedColor && sortedColors.some(c => c.toLowerCase() === selectedColor)) { + defaultColor = selectedColor; + } else if (sortedColors.length === 1) { + defaultColor = sortedColors[0].toLowerCase(); + } else { + defaultColor = 'white'; + } + + // Determine handle color based on door color + let handleColor = product.handleColor || 'silver'; + if (prodCode.toUpperCase() === 'COBRAI') { + const handleMap = { + 'black': 'black', + 'white': 'white', + 'bronze': 'bronze', + 'sandstone': 'bronze' + }; + handleColor = handleMap[defaultColor] || defaultColor; + } + + initialImage = buildFlatImagePath(product, defaultColor, handleColor); + } + + imageDisplayHtml = ` + ${title} + `; + } + + let html = ` +
+
${title}
+
+
+ ${imageDisplayHtml} +
+

Configure Your Product

+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+
+ +
+
+
+
+ Product Code: ${prodCode}
+ Category: ${category}

+ ${specsHtml} + ${description}

+ `; + + // Add features if available + if (features.length > 0) { + html += ` + Key Features: +
    + ${features.map(f => `
  • ${f}
  • `).join('')} +
+ `; + } + + if (compatibleAccessories.length > 0) { + html += ` +

Compatible Accessories

+
+ `; + compatibleAccessories.forEach(acc => { + html += ` +
+ ${acc.description}
+ Code: ${acc.accessoryCode} | ${acc.materials ? acc.materials.join(', ') : 'N/A'} +
+ `; + }); + html += `
`; + } + + html += ` +
+
+
+ + + +
+
+ `; + + contentDiv.innerHTML = html; + updateBreadcrumb(); + + // Initialize images based on type + if (hasLayeredImages && !hasFlatImages && !useDynamicAPI) { + updateProductPreview(prodCode); + } else if (hasFlatImages || useDynamicAPI) { + // Flat images or dynamic API - will update on dropdown change + // Initialize with first color if flat images + if (hasFlatImages && sortedColors.length > 0) { + updateFlatImagePreview(prodCode); + } + } +} + +// Update product preview based on configuration +function updateProductPreview(productCode) { + console.log('updateProductPreview called with:', productCode); + + const product = productData.find(p => p.productCode === productCode || p.id === productCode); + if (!product) return; + + // Check which system to use + const hasFlatImages = product.flatImages === true || product.imagePattern; + const useDynamicAPI = product.useDynamicAPI === true; + + console.log('Product flags - hasFlatImages:', hasFlatImages, 'useDynamicAPI:', useDynamicAPI); + + if (hasFlatImages) { + // Use pre-rendered flat images + updateFlatImagePreview(productCode); + return; + } + + if (useDynamicAPI) { + // Use dynamic image generation API + updateDynamicImagePreview(productCode); + return; + } + + // Otherwise use layered image system + if (!product.imageConfig || !product.imageConfig.layered) return; + + const basePath = product.imageConfig.basePath || ''; + const layers = product.imageConfig.layers || {}; + + // Get selected values + const selectedColor = document.getElementById('config-color')?.value || ''; + const selectedSize = document.getElementById('config-size')?.value || ''; + + const doorLayer = document.getElementById('door-layer'); + const overlayLayer = document.getElementById('overlay-layer'); + const notice = document.getElementById('config-notice'); + + let hasPreview = true; + + // Update door color layer + if (doorLayer && layers.door) { + if (selectedColor && layers.door[selectedColor]) { + doorLayer.src = basePath + layers.door[selectedColor]; + doorLayer.style.display = 'block'; + doorLayer.onerror = function() { + this.style.display = 'none'; + showConfigNotice(true); + }; + } else if (selectedColor) { + doorLayer.style.display = 'none'; + hasPreview = false; + } + } + + // Update overlay layer (if configuration dependent) + if (overlayLayer && layers.overlay) { + // You can add logic here based on door type, view, etc. + // For now, keep it hidden unless specifically needed + overlayLayer.style.display = 'none'; + } + + // Show/hide notice + showConfigNotice(!hasPreview && selectedColor); +} + +// Build flat image path based on product configuration and selections +function buildFlatImagePath(product, doorColor, handleColor = 'silver') { + const prodCode = product.productCode || product.id; + + // Option 1: Use custom image pattern if defined + if (product.imagePattern) { + return product.imagePattern + .replace('{productCode}', prodCode) + .replace('{doorColor}', doorColor) + .replace('{handleColor}', handleColor); + } + + // Option 2: Use custom image map if defined + if (product.imageMap && product.imageMap[doorColor]) { + return product.imageMap[doorColor]; + } + + // Option 3: Default naming convention: productCode-doorColor-handleColor + // Use relative path (Flask serves from app/images/) + return `images/${prodCode.toLowerCase()}-${doorColor.toLowerCase()}-${handleColor.toLowerCase()}.png`; +} + +// Update preview using pre-rendered flat images +function updateFlatImagePreview(productCode) { + const product = productData.find(p => p.productCode === productCode || p.id === productCode); + if (!product) return; + + const img = document.getElementById('product-detail-image'); + if (!img) return; + + // Get selected configuration + const doorColor = document.getElementById('config-color')?.value || 'white'; + const material = document.getElementById('config-material')?.value || 'aluminum'; + + console.log('updateFlatImagePreview - productCode:', productCode, 'doorColor:', doorColor); + + // Determine handle color based on door color (matching hardware to door) + let handleColor = product.handleColor || 'silver'; + + // For cobrai, map door colors to their matching handle colors + if (productCode.toUpperCase() === 'COBRAI') { + const handleMap = { + 'black': 'black', + 'white': 'white', + 'bronze': 'bronze', + 'sandstone': 'bronze' + }; + handleColor = handleMap[doorColor.toLowerCase()] || doorColor; + } + + // Build image path + const imagePath = buildFlatImagePath(product, doorColor, handleColor); + console.log('Image path:', imagePath); + + // Update image source (with fallback to original product image) + const fallbackImage = product.image || `https://via.placeholder.com/400x400/e0e0e0/666?text=${encodeURIComponent(productCode)}`; + img.onerror = function() { + console.log('Image failed to load:', imagePath); + this.onerror = null; // Prevent infinite loop + this.src = fallbackImage; + }; + img.src = imagePath; + console.log('Image src set to:', img.src); +} + +// Update preview using dynamic image API +function updateDynamicImagePreview(productCode) { + const img = document.getElementById('product-detail-image'); + if (!img) return; + + // Get selected configuration + const color = document.getElementById('config-color')?.value || 'white'; + const hinge = document.querySelector('[name="hinge-location"]:checked')?.value || 'right'; + const material = document.getElementById('config-material')?.value || 'aluminum'; + + // Build API URL + const apiUrl = `/api/product-image/${productCode}?color=${color}&hinge=${hinge}&material=${material}`; + + // Update image source + img.src = apiUrl; +} + +// Show or hide configuration notice +function showConfigNotice(show) { + const notice = document.getElementById('config-notice'); + if (notice) { + notice.style.display = show ? 'block' : 'none'; + } +} + +// Update hinge location (handles both flat and layered images) +function updateHingeLocation(flipLeft) { + // Check if current product uses dynamic API or flat images + const urlParams = new URLSearchParams(window.location.search); + const productCode = urlParams.get('p'); + + if (productCode) { + const product = productData.find(p => p.productCode === productCode || p.id === productCode); + + if (product) { + // For dynamic API, regenerate image with new hinge + if (product.useDynamicAPI === true) { + updateDynamicImagePreview(productCode); + return; + } + + // For flat images, just flip the image visually + if (product.flatImages === true || product.imagePattern) { + const flatImg = document.getElementById('product-detail-image'); + if (flatImg) { + flatImg.style.transform = flipLeft ? 'scaleX(-1)' : 'scaleX(1)'; + } + return; + } + } + } + + // Try flat image element + const flatImg = document.getElementById('product-detail-image'); + if (flatImg) { + flatImg.style.transform = flipLeft ? 'scaleX(-1)' : 'scaleX(1)'; + return; + } + + // Try layered images + const hardwareLayer = document.getElementById('hardware-layer'); + const doorLayer = document.getElementById('door-layer'); + + if (hardwareLayer) { + hardwareLayer.style.transform = flipLeft ? 'scaleX(-1)' : 'scaleX(1)'; + } + if (doorLayer) { + doorLayer.style.transform = flipLeft ? 'scaleX(-1)' : 'scaleX(1)'; + } +} + +// Flip product image based on hinge location (deprecated - use updateHingeLocation) +function flipProductImage(flip) { + updateHingeLocation(flip); +} + +// Handle size dropdown change to show/hide custom size fields +function handleSizeChange(productCode) { + const sizeSelect = document.getElementById('config-size'); + const customFields = document.getElementById('custom-size-fields'); + + if (sizeSelect && customFields) { + if (sizeSelect.value === 'custom') { + customFields.style.display = 'block'; + } else { + customFields.style.display = 'none'; + } + } + + // Update preview if not custom (custom doesn't affect image) + if (sizeSelect && sizeSelect.value !== 'custom') { + updateProductPreview(productCode); + } +} + +// Go back to results from product detail +function goBackToResults() { + // Find results in history or load it directly + const resultsIndex = history.indexOf('results'); + if (resultsIndex >= 0) { + history = history.slice(0, resultsIndex + 1); + } else { + // If not in history, add it + history.push('results'); + } + + // Update URL to results state + updateURL('results'); + showFilteredProducts(); +} + +// Share current page +function shareCurrentPage() { + const url = window.location.href; + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(url).then(() => { + showNotification('Link copied to clipboard!'); + }).catch(() => { + prompt('Copy this URL to share:', url); + }); + } else { + prompt('Copy this URL to share:', url); + } +} + +// Show filtered products based on user selections +function showFilteredProducts() { + const contentDiv = document.getElementById('content'); + + // Get filter criteria from userAnswers + let baseType = userAnswers['start']; + let subType = userAnswers['q-door-type'] || userAnswers['q-window-type']; + let material = null; + let color = null; + + // Extract material from any material questions + for (const key in userAnswers) { + if (key.includes('material') && userAnswers[key]) { + material = userAnswers[key]; + } + if (key.includes('color') && userAnswers[key]) { + color = userAnswers[key]; + } + } + + // Get dimensions + const width = userAnswers['q-dimensions.width']; + const height = userAnswers['q-dimensions.height']; + + console.log('Filtering with:', { baseType, subType, material, color, width, height }); + + // Filter products + let filtered = productData.filter(product => { + // Check base type + if (baseType && product.baseType !== baseType) return false; + + // Check sub-type + if (subType) { + const prodSubType = product.subType?.door || product.subType?.window; + if (prodSubType !== subType) return false; + } + + // Check material + if (material && !product.materials.includes(material)) return false; + + // Check color + if (color && !product.colors.includes(color)) return false; + + return true; + }); + + console.log('Filtered products:', filtered.length); + + // Display results + if (filtered.length === 0) { + contentDiv.innerHTML = ` +
+
No Products Found
+
+

We couldn't find any products matching your criteria:

+
    + ${baseType ? `
  • Type: ${baseType}
  • ` : ''} + ${subType ? `
  • Sub-type: ${subType}
  • ` : ''} + ${material ? `
  • Material: ${material}
  • ` : ''} + ${color ? `
  • Color: ${color}
  • ` : ''} + ${width ? `
  • Width: ${width}"
  • ` : ''} + ${height ? `
  • Height: ${height}"
  • ` : ''} +
+

Please try different options or contact us for assistance.

+
+
+ + +
+
+ `; + return; + } + + // Display product grid + let html = ` +
+
Found ${filtered.length} Product${filtered.length > 1 ? 's' : ''}
+
+

Your selections: + ${baseType || ''} ${subType ? '› ' + subType : ''} + ${material ? '› ' + material : ''} + ${color ? '› ' + color : ''} + ${width && height ? `› ${width}" Ɨ ${height}"` : ''} +

+
+
+ `; + + filtered.forEach(product => { + const prodCode = product.productCode || product.id; + const desc = product.description || product.DESCRIPTION || 'No description'; + const materials = product.materials ? product.materials.join(', ') : 'N/A'; + const colors = product.colors ? product.colors.join(', ') : 'N/A'; + + html += ` +
+

${desc}

+

${prodCode}

+
+ Materials: ${materials}
+ Colors: ${colors} +
+
+ `; + }); + + html += ` +
+
+ + + +
+
+ `; + + contentDiv.innerHTML = html; + updateBreadcrumb(); +} + +// Show notification +function showNotification(message) { + const notification = document.createElement('div'); + notification.className = 'notification'; + notification.textContent = message; + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #4CAF50; + color: white; + padding: 15px 20px; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + z-index: 10000; + animation: slideIn 0.3s ease-out; + `; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease-in'; + setTimeout(() => notification.remove(), 300); + }, 3000); +} + +// Handle browser back/forward buttons +window.addEventListener('popstate', function(event) { + if (event.state) { + if (event.state.productCode) { + showProductByCode(event.state.productCode); + } else if (event.state.questionKey) { + accumulatedBitValue = event.state.bitValue || 0; + loadContent(event.state.questionKey); + } else { + startOver(); + } + } else { + // No state, check URL + initFromURL(); + } +}); + +// Update breadcrumb trail +function updateBreadcrumb() { + const breadcrumb = document.getElementById('breadcrumb'); + const trail = history.map((key, index) => { + const data = questionData[key]; + let label = 'Start'; + + if (key === 'results') { + label = 'Results'; + } else if (key !== 'start') { + if (key.startsWith('product-')) { + // Product detail page + const productCode = key.replace('product-', ''); + label = productCode; + } else if (data && data.type === 'product') { + label = data.code; + } else if (data && data.title) { + // Extract meaningful part from question title + let title = data.title; + + // Remove common question prefixes and clean up + title = title.replace(/^What (are you looking for|type of|is your|do you)[\s?]*/i, ''); + title = title.replace(/^(Select|Choose|Enter)[\s]*/i, ''); + title = title.replace(/\?$/, ''); + + // Capitalize if needed + if (title.length > 0) { + title = title.charAt(0).toUpperCase() + title.slice(1); + } + + label = title || 'Question ' + index; + } else { + label = `Question ${index}`; + } + } + + if (index < history.length - 1) { + return `${label}`; + } else { + return `${label}`; + } + }); + + breadcrumb.innerHTML = trail.join(' → '); +} + +// Navigate to a specific point in history +function navigateToHistory(index) { + history = history.slice(0, index + 1); + loadContent(history[history.length - 1]); +} + +// Start the quiz when page loads +window.addEventListener('DOMContentLoaded', init); diff --git a/app/parse_csv_to_json.py b/app/parse_csv_to_json.py new file mode 100644 index 0000000..6f8eb17 --- /dev/null +++ b/app/parse_csv_to_json.py @@ -0,0 +1,652 @@ +""" +CSV to JSON Parser - Generate Navigation, Products, and Accessories JSON Files +Based on AI_PARSING_INSTRUCTIONS.md + +This script reads data/products.csv and generates: +1. data/navigation.json - Question flow with conditional logic +2. data/products.json - Product catalog +3. data/accessories.json - Accessory options with compatibility rules + +Run: python parse_csv_to_json.py +""" + +import csv +import json +from collections import defaultdict +from typing import Dict, List, Set, Tuple + +def parse_bool(value): + """Parse TRUE/FALSE string to boolean""" + if isinstance(value, str): + return value.strip().upper() == 'TRUE' + return bool(value) + +def clean_string(value): + """Clean and strip string values""" + if isinstance(value, str): + return value.strip() + return value or "" + +class CSVParser: + def __init__(self, csv_path: str): + self.csv_path = csv_path + self.products = [] + self.accessories = [] + self.base_types = set() + self.door_subtypes = defaultdict(list) # subtype -> [products] + self.window_subtypes = defaultdict(list) + self.material_counts = defaultdict(lambda: {'aluminum': 0, 'vinyl': 0}) + self.color_counts = defaultdict(lambda: defaultdict(int)) # subtype -> {color: count} + self.color_by_material = defaultdict(lambda: defaultdict(set)) # subtype -> {material: {colors}} + self.detailed_products = {} # Load from questions.json + + def load_detailed_product_info(self): + """Load detailed product info from questions.json""" + try: + # Load the code mapping + try: + with open('data/product_code_mapping.json', 'r', encoding='utf-8') as f: + code_mapping = json.load(f) + except FileNotFoundError: + code_mapping = {} + + with open('data/questions.json', 'r', encoding='utf-8') as f: + questions_data = json.load(f) + + # Extract all product nodes + for key, value in questions_data.items(): + if key.startswith('prod-') and value.get('type') == 'product': + code = value.get('code') + if code: + detailed_info = { + 'title': value.get('title'), + 'detailedDescription': value.get('description'), + 'features': value.get('features', []), + 'image': value.get('image'), + 'url': value.get('url'), + 'brochure': value.get('brochure'), + 'measureGuide': value.get('measureGuide') + } + + # Store under the simplified code + self.detailed_products[code] = detailed_info + + # Also store under all mapped codes + for csv_code, simple_code in code_mapping.items(): + if simple_code == code: + self.detailed_products[csv_code] = detailed_info + + print(f"āœ“ Loaded {len(self.detailed_products)} detailed product descriptions") + except FileNotFoundError: + print("⚠ questions.json not found - skipping detailed product info") + except Exception as e: + print(f"⚠ Error loading product details: {e}") + + def parse_csv(self): + """Parse the CSV file and separate products from accessories""" + print("šŸ“– Reading CSV file...") + + with open(self.csv_path, 'r', encoding='utf-8', errors='ignore') as f: + reader = csv.reader(f) + + # Skip first two header rows + next(reader) # Row 1: Category headers + header_row = next(reader) # Row 2: Column names + + row_count = 0 + for row in reader: + if len(row) < 11: # Need at least up to column K + continue + + row_count += 1 + + # Skip discontinued items + if parse_bool(row[0]): # DISCONT column + continue + + # Extract basic info + prod_code = clean_string(row[2]) # PROD_CODE + category = clean_string(row[3]) # CATEGORY + description = clean_string(row[5]) # DESCRIPTION + location = clean_string(row[1]) # LOC_CODE + + base_type = clean_string(row[6]) if len(row) > 6 else "" # Base Type (G) + door_subtype = clean_string(row[7]) if len(row) > 7 else "" # Sub-type Door (H) + window_subtype = clean_string(row[8]) if len(row) > 8 else "" # Sub-type Window (I) + is_accessory = parse_bool(row[9]) if len(row) > 9 else False # Accessory Yes (J) + specific_item = parse_bool(row[10]) if len(row) > 10 else False # This Item (K) + + # Parse materials (L, M) + materials = [] + if len(row) > 11 and parse_bool(row[11]): + materials.append('Aluminum') + if len(row) > 12 and parse_bool(row[12]): + materials.append('Vinyl') + + # Parse colors (N through S+) + colors = [] + color_columns = [ + (13, 'Black'), (14, 'White'), (15, 'Bronze'), + (16, 'Tan'), (17, 'Mill'), (18, 'Sandstone') + ] + for col_idx, color_name in color_columns: + if len(row) > col_idx and parse_bool(row[col_idx]): + colors.append(color_name) + + # Build item data + item_data = { + 'id': prod_code, + 'productCode': prod_code, + 'category': category, + 'description': description, + 'discontinued': False, + 'location': location, + 'baseType': base_type, + 'subType': { + 'door': door_subtype if door_subtype else None, + 'window': window_subtype if window_subtype else None + }, + 'materials': materials, + 'colors': colors + } + + # Separate accessories from products + if is_accessory: + # Build accessory-specific data + accessory = { + **item_data, + 'accessoryCode': prod_code, + 'compatibilityRules': self._build_compatibility_rules( + door_subtype, window_subtype, category, specific_item + ), + 'optionType': self._infer_option_type(description), + 'metadata': { + 'specificItemLink': specific_item + } + } + self.accessories.append(accessory) + else: + # Build product-specific data + product = { + **item_data, + 'isAccessory': False, + 'compatibleAccessories': [] # Will be populated later + } + + # Merge detailed info if available + if prod_code in self.detailed_products: + detailed = self.detailed_products[prod_code] + product.update({ + 'title': detailed['title'], + 'detailedDescription': detailed['detailedDescription'], + 'features': detailed['features'], + 'image': detailed['image'], + 'url': detailed['url'], + 'brochure': detailed.get('brochure'), + 'measureGuide': detailed.get('measureGuide') + }) + + self.products.append(product) + + # Track for navigation building + if base_type: + self.base_types.add(base_type) + + # Track subtypes per type + if door_subtype: + self.door_subtypes[door_subtype].append(product) + if window_subtype: + self.window_subtypes[window_subtype].append(product) + + # Count materials per subtype + subtype_key = door_subtype or window_subtype or "none" + if 'Aluminum' in materials: + self.material_counts[subtype_key]['aluminum'] += 1 + # Track colors per material + for color in colors: + self.color_by_material[subtype_key]['Aluminum'].add(color) + if 'Vinyl' in materials: + self.material_counts[subtype_key]['vinyl'] += 1 + # Track colors per material + for color in colors: + self.color_by_material[subtype_key]['Vinyl'].add(color) + + # Count colors per subtype (overall) + for color in colors: + self.color_counts[subtype_key][color] += 1 + + print(f"āœ“ Parsed {row_count} rows") + print(f"āœ“ Found {len(self.products)} products") + print(f"āœ“ Found {len(self.accessories)} accessories") + + def _build_compatibility_rules(self, door_subtype, window_subtype, category, specific_item): + """Build compatibility rules for accessories""" + rules = { + 'type': 'category', # Default + 'subTypeDoor': door_subtype if door_subtype else None, + 'subTypeWindow': window_subtype if window_subtype else None, + 'categories': [category] if category else [], + 'specificProducts': [], + 'requiresMatch': { + 'material': True, # Usually accessories need matching material + 'color': False # Colors usually don't need to match + } + } + + # Determine rule type + if specific_item: + rules['type'] = 'specific' + elif door_subtype or window_subtype: + rules['type'] = 'subType' + + return rules + + def _infer_option_type(self, description): + """Infer option type from description""" + desc_upper = description.upper() + + if 'HANDLE' in desc_upper: + return 'handle' + elif 'INSERT' in desc_upper or 'GLASS' in desc_upper: + return 'insert' + elif 'HARDWARE' in desc_upper: + return 'hardware' + elif 'SCREEN' in desc_upper: + return 'screen' + else: + return 'option' + + def link_accessories_to_products(self): + """Link compatible accessories to products""" + print("šŸ”— Linking accessories to products...") + + linked_count = 0 + for product in self.products: + compatible = [] + + for accessory in self.accessories: + rules = accessory['compatibilityRules'] + + # Check by subtype + if rules['type'] == 'subType': + prod_door = product['subType']['door'] + prod_window = product['subType']['window'] + acc_door = rules['subTypeDoor'] + acc_window = rules['subTypeWindow'] + + if (prod_door and prod_door == acc_door) or \ + (prod_window and prod_window == acc_window): + # Check material compatibility if required + if rules['requiresMatch']['material']: + # Check if they share any materials + if any(m in product['materials'] for m in accessory['materials']): + compatible.append(accessory['id']) + linked_count += 1 + else: + compatible.append(accessory['id']) + linked_count += 1 + + # Check by category + elif rules['type'] == 'category': + if product['category'] in rules['categories']: + compatible.append(accessory['id']) + linked_count += 1 + + product['compatibleAccessories'] = compatible + + print(f"āœ“ Created {linked_count} product-accessory links") + + def build_navigation(self): + """Build navigation JSON with conditional logic""" + print("šŸ—ŗļø Building navigation flow...") + + navigation = {} + + # Question 1: Base Type Selection + navigation['start'] = { + 'type': 'question', + 'inputType': 'button', + 'title': 'What are you looking for?', + 'subtitle': 'Select the product category', + 'answers': [] + } + + # Add base type options + sorted_types = sorted(self.base_types) + for base_type in sorted_types: + emoji = '🚪' if 'door' in base_type.lower() else '🪟' + next_key = f'q-{base_type.lower()}-type' + + navigation['start']['answers'].append({ + 'caption': base_type, + 'image': emoji, + 'next': next_key, + 'filter': { + 'baseType': base_type + } + }) + + # Question 2: Sub-type Selection for Doors + if self.door_subtypes: + door_subtypes = sorted(self.door_subtypes.keys()) + navigation['q-door-type'] = { + 'type': 'question', + 'inputType': 'button', + 'title': 'What type of door?', + 'subtitle': 'Select the door category', + 'answers': [] + } + + for subtype in door_subtypes: + products = self.door_subtypes[subtype] + + # Check if material question is needed + needs_material = self._needs_material_question(subtype) + + # Determine next question + if needs_material: + next_key = f'q-material-{subtype.lower().replace(" ", "-")}' + else: + # No material choice needed - determine single material and check colors + single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl' + needs_color = self._needs_color_question(subtype, single_material) + next_key = f'q-color-{subtype.lower().replace(" ", "-")}-{single_material.lower()}' if needs_color else 'q-dimensions' + + # Auto-apply material if only one + filter_obj = { + 'baseType': 'Door', + 'subType': subtype + } + + if not needs_material: + # Auto-apply the single material + if self.material_counts[subtype]['aluminum'] > 0: + filter_obj['material'] = 'Aluminum' + elif self.material_counts[subtype]['vinyl'] > 0: + filter_obj['material'] = 'Vinyl' + + # If no color question either, auto-apply single color + single_material = filter_obj['material'] + if not self._needs_color_question(subtype, single_material): + colors = list(self.color_by_material[subtype][single_material]) + if len(colors) == 1: + filter_obj['color'] = colors[0] + + navigation['q-door-type']['answers'].append({ + 'caption': subtype, + 'image': '🚪', + 'next': next_key, + 'filter': filter_obj + }) + + # Create material question if needed + if needs_material: + self._create_material_question(navigation, subtype, 'Door') + # Create color question if material skipped but color needed + elif next_key.startswith('q-color-'): + single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl' + self._create_color_question(navigation, subtype, single_material, 'Door') + + # Question 2: Sub-type Selection for Windows + if self.window_subtypes: + window_subtypes = sorted(self.window_subtypes.keys()) + navigation['q-window-type'] = { + 'type': 'question', + 'inputType': 'button', + 'title': 'What type of window?', + 'subtitle': 'Select the window category', + 'answers': [] + } + + for subtype in window_subtypes: + needs_material = self._needs_material_question(subtype) + + # Determine next question + if needs_material: + next_key = f'q-material-{subtype.lower().replace(" ", "-")}' + else: + # No material choice needed - determine single material and check colors + single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl' + needs_color = self._needs_color_question(subtype, single_material) + next_key = f'q-color-{subtype.lower().replace(" ", "-")}-{single_material.lower()}' if needs_color else 'q-dimensions' + + filter_obj = { + 'baseType': 'Window', + 'subType': subtype + } + + if not needs_material: + if self.material_counts[subtype]['aluminum'] > 0: + filter_obj['material'] = 'Aluminum' + elif self.material_counts[subtype]['vinyl'] > 0: + filter_obj['material'] = 'Vinyl' + + # If no color question either, auto-apply single color + single_material = filter_obj['material'] + if not self._needs_color_question(subtype, single_material): + colors = list(self.color_by_material[subtype][single_material]) + if len(colors) == 1: + filter_obj['color'] = colors[0] + + navigation['q-window-type']['answers'].append({ + 'caption': subtype, + 'image': '🪟', + 'next': next_key, + 'filter': filter_obj + }) + + if needs_material: + self._create_material_question(navigation, subtype, 'Window') + # Create color question if material skipped but color needed + elif next_key.startswith('q-color-'): + single_material = 'Aluminum' if self.material_counts[subtype]['aluminum'] > 0 else 'Vinyl' + self._create_color_question(navigation, subtype, single_material, 'Window') + + # Final Question: Dimensions + navigation['q-dimensions'] = { + 'type': 'question', + 'inputType': 'form', + 'title': 'Enter Product Dimensions', + 'subtitle': 'Please provide the measurements', + 'fields': [ + { + 'name': 'width', + 'label': 'Width (inches)', + 'type': 'number', + 'required': True, + 'placeholder': 'e.g., 36' + }, + { + 'name': 'height', + 'label': 'Height (inches)', + 'type': 'number', + 'required': True, + 'placeholder': 'e.g., 80' + } + ], + 'next': 'results' + } + + print(f"āœ“ Created {len(navigation)} navigation nodes") + return navigation + + def _needs_material_question(self, subtype): + """Check if a subtype needs a material selection question""" + aluminum = self.material_counts[subtype]['aluminum'] + vinyl = self.material_counts[subtype]['vinyl'] + + # Show material question if both materials exist + return aluminum > 0 and vinyl > 0 + + def _create_material_question(self, navigation, subtype, base_type): + """Create a material selection question""" + question_key = f'q-material-{subtype.lower().replace(" ", "-")}' + + navigation[question_key] = { + 'type': 'question', + 'inputType': 'button', + 'title': 'Select Material', + 'subtitle': f'Choose your preferred material for {subtype}', + 'conditional': { + 'type': 'material', + 'fallbackNext': 'q-dimensions' + }, + 'answers': [] + } + + # Add material options + if self.material_counts[subtype]['aluminum'] > 0: + # Check if color question needed after aluminum selection + needs_color = self._needs_color_question(subtype, 'Aluminum') + next_key_al = f'q-color-{subtype.lower().replace(" ", "-")}-aluminum' if needs_color else 'q-dimensions' + + navigation[question_key]['answers'].append({ + 'caption': 'Aluminum', + 'image': 'šŸ”©', + 'next': next_key_al, + 'filter': { + 'baseType': base_type, + 'subType': subtype, + 'material': 'Aluminum' + } + }) + + if self.material_counts[subtype]['vinyl'] > 0: + # Check if color question needed after vinyl selection + needs_color = self._needs_color_question(subtype, 'Vinyl') + next_key_vin = f'q-color-{subtype.lower().replace(" ", "-")}-vinyl' if needs_color else 'q-dimensions' + + navigation[question_key]['answers'].append({ + 'caption': 'Vinyl', + 'image': '🪟', + 'next': next_key_vin, + 'filter': { + 'baseType': base_type, + 'subType': subtype, + 'material': 'Vinyl' + } + }) + + # Create color questions for each material option + if self.material_counts[subtype]['aluminum'] > 0 and self._needs_color_question(subtype, 'Aluminum'): + self._create_color_question(navigation, subtype, 'Aluminum', base_type) + if self.material_counts[subtype]['vinyl'] > 0 and self._needs_color_question(subtype, 'Vinyl'): + self._create_color_question(navigation, subtype, 'Vinyl', base_type) + + def _needs_color_question(self, subtype, material): + """Check if a subtype+material combination needs a color selection question""" + colors = self.color_by_material[subtype][material] + # Show color question if multiple colors exist + return len(colors) > 1 + + def _create_color_question(self, navigation, subtype, material, base_type): + """Create a color selection question""" + question_key = f'q-color-{subtype.lower().replace(" ", "-")}-{material.lower()}' + + navigation[question_key] = { + 'type': 'question', + 'inputType': 'button', + 'title': 'Select Color', + 'subtitle': f'Choose your preferred color for {material} {subtype}', + 'conditional': { + 'type': 'color', + 'fallbackNext': 'q-dimensions' + }, + 'answers': [] + } + + # Get available colors for this subtype+material + colors = sorted(self.color_by_material[subtype][material]) + + # Color emoji mapping + color_emojis = { + 'Black': '⬛', + 'White': '⬜', + 'Bronze': '🟫', + 'Tan': '🟤', + 'Mill': '⚪', + 'Sandstone': '🟨' + } + + for color in colors: + navigation[question_key]['answers'].append({ + 'caption': color, + 'image': color_emojis.get(color, 'šŸŽØ'), + 'next': 'q-dimensions', + 'filter': { + 'baseType': base_type, + 'subType': subtype, + 'material': material, + 'color': color + } + }) + + def save_json_files(self, navigation): + """Save all three JSON files""" + print("šŸ’¾ Saving JSON files...") + + # Save products.json + with open('data/products.json', 'w', encoding='utf-8') as f: + json.dump(self.products, f, indent=2) + print(f"āœ“ Saved data/products.json ({len(self.products)} products)") + + # Save accessories.json + with open('data/accessories.json', 'w', encoding='utf-8') as f: + json.dump(self.accessories, f, indent=2) + print(f"āœ“ Saved data/accessories.json ({len(self.accessories)} accessories)") + + # Save navigation.json + with open('data/navigation.json', 'w', encoding='utf-8') as f: + json.dump(navigation, f, indent=2) + print(f"āœ“ Saved data/navigation.json ({len(navigation)} nodes)") + + def print_statistics(self): + """Print parsing statistics""" + print("\n" + "="*60) + print("PARSING STATISTICS") + print("="*60) + print(f"Total Products: {len(self.products)}") + print(f"Total Accessories: {len(self.accessories)}") + print(f"Base Types: {', '.join(sorted(self.base_types))}") + print(f"Door Subtypes: {len(self.door_subtypes)}") + print(f"Window Subtypes: {len(self.window_subtypes)}") + print() + print("Material Distribution:") + for subtype, counts in sorted(self.material_counts.items()): + if counts['aluminum'] > 0 or counts['vinyl'] > 0: + needs_q = "āœ“ Material Q" if (counts['aluminum'] > 0 and counts['vinyl'] > 0) else "āœ— Skip" + print(f" {subtype:20} - Al:{counts['aluminum']:3} Vinyl:{counts['vinyl']:3} [{needs_q}]") + print("="*60) + +def main(): + csv_path = 'data/products.csv' + + print("šŸš€ CSV to JSON Parser") + print("="*60) + + try: + parser = CSVParser(csv_path) + parser.load_detailed_product_info() + parser.parse_csv() + parser.link_accessories_to_products() + navigation = parser.build_navigation() + parser.save_json_files(navigation) + parser.print_statistics() + + print("\nāœ… All JSON files generated successfully!") + print("\nNext steps:") + print("1. Review data/navigation.json for question flow") + print("2. Review data/products.json for product data") + print("3. Review data/accessories.json for accessory compatibility") + print("4. Test the application in your browser") + + except FileNotFoundError: + print(f"āŒ Error: Could not find {csv_path}") + print(" Make sure products.csv exists in the data/ folder") + except Exception as e: + print(f"āŒ Error: {e}") + import traceback + traceback.print_exc() + +if __name__ == '__main__': + main() diff --git a/app/passenger_wsgi.py b/app/passenger_wsgi.py new file mode 100644 index 0000000..90d062f --- /dev/null +++ b/app/passenger_wsgi.py @@ -0,0 +1,47 @@ +""" +Passenger WSGI file for Flask application +Compatible with Python 3.13+ +Auto-detects /product-finder subdirectory deployment +""" +import sys +import os + +# Get the directory where this file is located +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Add the application directory to Python path +sys.path.insert(0, CURRENT_DIR) + +# Import the Flask application directly from app.py +# Note: app.py now auto-detects production environment +try: + from app import app as application + print("āœ“ Flask application loaded successfully") + +except ImportError as e: + # Fallback error handler that shows the actual error + def application(environ, start_response): + import traceback + status = '500 Internal Server Error' + error_details = f'Import Error: {str(e)}\n\nTraceback:\n{traceback.format_exc()}\n\nPython Path:\n{sys.path}' + output = error_details.encode('utf-8') + response_headers = [('Content-type', 'text/plain; charset=utf-8'), + ('Content-Length', str(len(output)))] + start_response(status, response_headers) + return [output] + print(f"āœ— Failed to import Flask app: {e}") + +except Exception as e: + # Catch any other startup errors + def application(environ, start_response): + import traceback + status = '500 Internal Server Error' + error_details = f'Startup Error: {str(e)}\n\nTraceback:\n{traceback.format_exc()}' + output = error_details.encode('utf-8') + response_headers = [('Content-type', 'text/plain; charset=utf-8'), + ('Content-Length', str(len(output)))] + start_response(status, response_headers) + return [output] + print(f"āœ— Error starting app: {e}") + +# Passenger requires the application object to be named 'application' diff --git a/app/region_helper.py b/app/region_helper.py new file mode 100644 index 0000000..0956860 --- /dev/null +++ b/app/region_helper.py @@ -0,0 +1,189 @@ +""" +Interactive Region Coordinate Helper +Opens an image and helps you find pixel coordinates for colorable regions. +Click on the image to see coordinates. + +Usage: + python region_helper.py images/cobrai.jpg +""" + +import sys +from PIL import Image, ImageDraw, ImageFont +import tkinter as tk +from tkinter import Canvas +from PIL import ImageTk + + +class RegionHelper: + def __init__(self, image_path): + self.image_path = image_path + self.image = Image.open(image_path) + self.width, self.height = self.image.size + + # Setup UI + self.root = tk.Tk() + self.root.title(f"Region Helper - {image_path}") + + # Scale image if too large + self.scale = 1.0 + max_display_width = 1000 + max_display_height = 800 + + if self.width > max_display_width or self.height > max_display_height: + scale_w = max_display_width / self.width + scale_h = max_display_height / self.height + self.scale = min(scale_w, scale_h) + + new_width = int(self.width * self.scale) + new_height = int(self.height * self.scale) + self.display_image = self.image.resize((new_width, new_height), Image.LANCZOS) + else: + self.display_image = self.image.copy() + + # Setup canvas + self.canvas = Canvas( + self.root, + width=self.display_image.width, + height=self.display_image.height + ) + self.canvas.pack(side=tk.LEFT) + + # Display image + self.photo = ImageTk.PhotoImage(self.display_image) + self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo) + + # Info panel + info_frame = tk.Frame(self.root, width=300) + info_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=10, pady=10) + + tk.Label(info_frame, text="Image Dimensions:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=5) + tk.Label(info_frame, text=f"{self.width} x {self.height} pixels").pack(anchor=tk.W) + + if self.scale != 1.0: + tk.Label(info_frame, text=f"Scaled: {self.scale:.2%}").pack(anchor=tk.W) + + tk.Label(info_frame, text="\nClick to get coordinates:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(20, 5)) + + self.coord_label = tk.Label(info_frame, text="X: -, Y: -", font=('Arial', 14)) + self.coord_label.pack(anchor=tk.W, pady=5) + + tk.Label(info_frame, text="\nFirst click = Top-Left", fg='blue').pack(anchor=tk.W) + tk.Label(info_frame, text="Second click = Bottom-Right", fg='blue').pack(anchor=tk.W) + + tk.Label(info_frame, text="\nRegion JSON:", font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(20, 5)) + + self.json_text = tk.Text(info_frame, height=15, width=35, font=('Courier', 9)) + self.json_text.pack(fill=tk.BOTH, expand=True) + + tk.Button(info_frame, text="Clear Points", command=self.clear_points).pack(pady=10) + tk.Button(info_frame, text="Copy JSON", command=self.copy_json).pack() + + # Bind events + self.canvas.bind('', self.on_mouse_move) + self.canvas.bind('', self.on_click) + + # Store points + self.points = [] + self.point_ids = [] + + self.update_json() + + def on_mouse_move(self, event): + """Show current mouse coordinates.""" + x = int(event.x / self.scale) + y = int(event.y / self.scale) + self.coord_label.config(text=f"X: {x}, Y: {y}") + + def on_click(self, event): + """Record clicked point.""" + x = int(event.x / self.scale) + y = int(event.y / self.scale) + + # Limit to 2 points (top-left, bottom-right) + if len(self.points) >= 2: + self.clear_points() + + self.points.append((x, y)) + + # Draw point on canvas + color = 'blue' if len(self.points) == 1 else 'red' + point_id = self.canvas.create_oval( + event.x - 5, event.y - 5, + event.x + 5, event.y + 5, + fill=color, outline='white', width=2 + ) + self.point_ids.append(point_id) + + # Draw rectangle if we have both points + if len(self.points) == 2: + x1, y1 = self.points[0] + x2, y2 = self.points[1] + + # Draw on canvas (scaled) + rect_id = self.canvas.create_rectangle( + x1 * self.scale, y1 * self.scale, + x2 * self.scale, y2 * self.scale, + outline='green', width=2, dash=(5, 5) + ) + self.point_ids.append(rect_id) + + self.update_json() + + def clear_points(self): + """Clear all points and rectangles.""" + for point_id in self.point_ids: + self.canvas.delete(point_id) + + self.points = [] + self.point_ids = [] + self.update_json() + + def update_json(self): + """Update the JSON display.""" + self.json_text.delete('1.0', tk.END) + + if len(self.points) == 0: + self.json_text.insert('1.0', '{\n "name": "region_name",\n "topLeft": [?, ?],\n "bottomRight": [?, ?],\n "description": "..."\n}') + elif len(self.points) == 1: + x, y = self.points[0] + self.json_text.insert('1.0', f'{{\n "name": "region_name",\n "topLeft": [{x}, {y}],\n "bottomRight": [?, ?],\n "description": "..."\n}}') + else: + x1, y1 = self.points[0] + x2, y2 = self.points[1] + + # Calculate dimensions + width = abs(x2 - x1) + height = abs(y2 - y1) + + json_str = f'''{{\n "name": "region_name",\n "topLeft": [{x1}, {y1}],\n "bottomRight": [{x2}, {y2}],\n "description": "Width: {width}px, Height: {height}px"\n}}''' + self.json_text.insert('1.0', json_str) + + def copy_json(self): + """Copy JSON to clipboard.""" + json_str = self.json_text.get('1.0', tk.END).strip() + self.root.clipboard_clear() + self.root.clipboard_append(json_str) + self.root.update() + + def run(self): + """Start the GUI.""" + self.root.mainloop() + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: python region_helper.py ") + print("Example: python region_helper.py images/cobrai.jpg") + sys.exit(1) + + image_path = sys.argv[1] + + try: + helper = RegionHelper(image_path) + helper.run() + except FileNotFoundError: + print(f"Error: Image file not found: {image_path}") + sys.exit(1) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..0d5627b --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,4 @@ +Flask>=3.0.0 +Werkzeug>=3.0.0 +# Pillow>=10.0.0 # Optional - only needed for dynamic image generation +# If you need image generation, install separately: pip install Pillow diff --git a/app/templates/404.html b/app/templates/404.html new file mode 100644 index 0000000..2a10638 --- /dev/null +++ b/app/templates/404.html @@ -0,0 +1,77 @@ + + + + + + 404 - Page Not Found + + + +
+

404

+

Page Not Found

+

Sorry, the page you're looking for doesn't exist or has been moved.

+ Go Home + Start Quiz +
+ + diff --git a/app/templates/access_denied.html b/app/templates/access_denied.html new file mode 100644 index 0000000..e0b0029 --- /dev/null +++ b/app/templates/access_denied.html @@ -0,0 +1,133 @@ + + + + + + Access Denied - CGW Product Finder + + + +
+
🚫
+

Access Denied

+

+ You do not have permission to access this page or perform this action. +

+ + {% if required_permission %} +
+ Required Permission: {{ required_permission }} +
+ {% endif %} + +

+ If you believe this is an error, please contact your administrator. +

+ + + +
+ Need help? Contact your system administrator to request access. +
+
+ + diff --git a/app/templates/image_test.html b/app/templates/image_test.html new file mode 100644 index 0000000..6a30037 --- /dev/null +++ b/app/templates/image_test.html @@ -0,0 +1,381 @@ + + + + + + Dynamic Image Generation - Test Page + + + +
+

šŸŽØ Dynamic Image Generation API

+

Test the product image generation endpoints

+ +
+

How It Works

+

+ This demo uses server-side image processing to dynamically generate product images + with different colors and configurations. Select options below to see the image update in real-time. +

+
+ +
+

Product Configuration

+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+ + + + +
+ +
+
+

Direct Image URL Method

+

+ Using: <img src="..."> +

+ Product +
Loading...
+
+ +
+

JSON/Base64 Method

+

+ Using: fetch() + base64 +

+ Product +
Loading...
+
+
+ + + + + +
+

API Endpoints Used

+
    +
  • GET /api/product-image/{product}?color={color}&hinge={hinge}
  • +
  • GET /api/product-image/{product}?color={color}&hinge={hinge}&format=json
  • +
  • GET /api/product-config/{product}
  • +
  • POST /api/clear-image-cache
  • +
+
+
+ + + + diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..be21f87 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,230 @@ + + + + + + Product Details Form + + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + + +
+ + +
+ + + +
+

Possible questions for customers based on what is selected or missing

+
[list - checkbox per question]
+
+
+ + + + diff --git a/app/templates/index2.html b/app/templates/index2.html new file mode 100644 index 0000000..b6ce836 --- /dev/null +++ b/app/templates/index2.html @@ -0,0 +1,75 @@ + + + + + + Product Finder + + + +
+
+

Product Finder

+ +
+ + + +
+ +
+
+ + + + + + diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..63b4662 --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,255 @@ + + + + + + Login - CGW Product Finder + + + + + + + + diff --git a/app/templates/select_location.html b/app/templates/select_location.html new file mode 100644 index 0000000..bc9ef2f --- /dev/null +++ b/app/templates/select_location.html @@ -0,0 +1,359 @@ + + + + + + Select Location - CGW Product Finder + + + +
+
+

šŸ“ Select Your Location

+

Choose a location to access the Product Finder

+
+ + + +
+ +
+
+ +
+ + +
+ + + + +
+ + + + diff --git a/app/templates/user_manager.html b/app/templates/user_manager.html new file mode 100644 index 0000000..5056936 --- /dev/null +++ b/app/templates/user_manager.html @@ -0,0 +1,976 @@ + + + + + + User Manager - CGW Product Finder + + + +
+
+

šŸ” User Manager

+

Create and manage user accounts with secure password encoding

+
+ +
+
+ +
+

Add New User

+
+
+ + +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Default Location * + +
+ + + +
+ + + +
+ + + +
+ + + +
+
+ + +
+ +
+
+
0
+
Total Users
+
+
+
+ +
+

User List

+
+

No users added yet.

+
+ +
+ + +
+
+ + ← Back to Product Finder +
+ + + + + + + diff --git a/app/test_app.py b/app/test_app.py new file mode 100644 index 0000000..59dba54 --- /dev/null +++ b/app/test_app.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +""" +Comprehensive app test script +Run this through control panel to diagnose issues +Writes detailed logs to test_app.log +""" +import sys +import os +from datetime import datetime + +# Setup logging +log_file = os.path.join(os.path.dirname(__file__), 'test_app.log') + +def log(message, also_print=True): + """Write to log file and optionally print""" + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + full_message = f"[{timestamp}] {message}" + + with open(log_file, 'a') as f: + f.write(full_message + '\n') + + if also_print: + print(full_message) + +try: + log("="*70) + log("APP TEST SCRIPT - START") + log("="*70) + + # 1. Environment Info + log("\n1. PYTHON ENVIRONMENT") + log(f" Python: {sys.version}") + log(f" Executable: {sys.executable}") + log(f" CWD: {os.getcwd()}") + + # 2. Check environment variables + log("\n2. ENVIRONMENT VARIABLES") + log(f" APPLICATION_ROOT: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}") + log(f" FLASK_ENV: {os.environ.get('FLASK_ENV', 'NOT SET')}") + log(f" SECRET_KEY: {'SET' if os.environ.get('SECRET_KEY') else 'NOT SET'}") + + # 3. Check critical files + log("\n3. FILE STRUCTURE CHECK") + files_to_check = [ + 'app.py', + 'passenger_wsgi.py', + 'config.py', + 'data/users.json', + 'templates/login.html', + 'templates/index2.html', + 'templates/select_location.html', + 'templates/user_manager.html' + ] + + all_files_exist = True + for file_path in files_to_check: + full_path = os.path.join(os.path.dirname(__file__), file_path) + exists = os.path.exists(full_path) + status = "āœ“" if exists else "āœ— MISSING" + log(f" {status} {file_path}") + if not exists: + all_files_exist = False + + if not all_files_exist: + log("\nāš ļø CRITICAL: Missing files detected!") + + # 4. Check dependencies + log("\n4. PYTHON PACKAGES") + packages = [ + ('flask', 'Flask'), + ('werkzeug', 'Werkzeug') + ] + + all_packages_ok = True + for module_name, display_name in packages: + try: + module = __import__(module_name) + try: + from importlib.metadata import version + ver = version(module_name) + except: + ver = getattr(module, '__version__', 'unknown') + log(f" āœ“ {display_name}: {ver}") + except ImportError as e: + log(f" āœ— {display_name}: NOT INSTALLED ({e})") + all_packages_ok = False + + if not all_packages_ok: + log("\nāš ļø CRITICAL: Missing packages! Run: pip install Flask==3.0.0 Werkzeug==3.0.1") + sys.exit(1) + + # 5. Try importing app + log("\n5. APP IMPORT TEST") + log(" Attempting to import Flask app...") + + try: + # Make sure current directory is in path + current_dir = os.path.dirname(__file__) + if current_dir not in sys.path: + sys.path.insert(0, current_dir) + + from app import app + log(" āœ“ Flask app imported successfully!") + + # Check app config + log("\n6. APP CONFIGURATION") + log(f" APPLICATION_ROOT: {app.config.get('APPLICATION_ROOT', 'NOT SET')}") + log(f" DEBUG: {app.config.get('DEBUG')}") + log(f" SECRET_KEY: {'SET (' + str(len(app.config.get('SECRET_KEY', ''))) + ' chars)' if app.config.get('SECRET_KEY') else 'NOT SET'}") + + # Check if middleware applied + log(f" WSGI App Type: {type(app.wsgi_app).__name__}") + if 'PrefixMiddleware' in str(type(app.wsgi_app)): + log(" āœ“ PrefixMiddleware is active") + else: + log(" āš ļø PrefixMiddleware NOT active (may cause issues at /product-finder)") + + # Count routes + log("\n7. ROUTES CHECK") + routes = list(app.url_map.iter_rules()) + log(f" Total routes: {len(routes)}") + + # Check critical routes + critical_routes = ['/login', '/api/login', '/users', '/select-location'] + log(" Critical routes:") + for route_path in critical_routes: + found = any(str(rule) == route_path or str(rule).startswith(route_path + '<') for rule in routes) + status = "āœ“" if found else "āœ— MISSING" + log(f" {status} {route_path}") + + # 8. Test loading users + log("\n8. USER DATA TEST") + try: + users_file = os.path.join(current_dir, 'data', 'users.json') + if os.path.exists(users_file): + import json + with open(users_file, 'r') as f: + users = json.load(f) + log(f" āœ“ Users file loaded: {len(users)} users") + log(f" Usernames: {', '.join([u.get('username', '?') for u in users])}") + else: + log(f" āœ— Users file NOT FOUND at {users_file}") + except Exception as e: + log(f" āœ— Error loading users: {e}") + + # 9. Test a simple request + log("\n9. REQUEST TEST") + log(" Testing if app can handle requests...") + + with app.test_client() as client: + # Test root redirect + try: + response = client.get('/') + log(f" GET / -> Status: {response.status_code}") + if response.status_code == 302: + log(f" Redirects to: {response.location}") + except Exception as e: + log(f" āœ— Error testing /: {e}") + + # Test login page + try: + response = client.get('/login') + log(f" GET /login -> Status: {response.status_code}") + if response.status_code != 200: + log(f" āš ļø Login page returned {response.status_code} instead of 200") + except Exception as e: + log(f" āœ— Error testing /login: {e}") + + # 10. Summary + log("\n" + "="*70) + log("TEST SUMMARY") + log("="*70) + + if all_files_exist and all_packages_ok: + log("āœ“ All files present") + log("āœ“ All packages installed") + log("āœ“ App imports successfully") + log(f"āœ“ {len(routes)} routes registered") + + app_root = app.config.get('APPLICATION_ROOT', '/') + if app_root == '/product-finder': + log("āœ“ Configured for /product-finder deployment") + else: + log(f"āš ļø APPLICATION_ROOT is '{app_root}' (should be '/product-finder' for production)") + + log("\nšŸŽ‰ APP APPEARS READY!") + log("\nIf still getting 500 errors:") + log("1. Check that data/users.json exists") + log("2. Verify APPLICATION_ROOT env var is set to /product-finder") + log("3. Check Apache error log for runtime errors") + log("4. Ensure .htaccess is correct") + + else: + log("\nāš ļø ISSUES FOUND - Fix these before deployment") + + log("="*70) + log(f"\nLog saved to: {log_file}") + + except ImportError as e: + log(f" āœ— FAILED to import app!") + log(f" Error: {e}") + + import traceback + log("\nFull traceback:") + log(traceback.format_exc()) + + log("\nāŒ CRITICAL ERROR: Cannot import Flask app") + log("Check that app.py exists and has no syntax errors") + + except Exception as e: + log(f" āœ— Unexpected error during import!") + log(f" Error: {e}") + + import traceback + log("\nFull traceback:") + log(traceback.format_exc()) + +except Exception as e: + log(f"\nāŒ FATAL ERROR: {e}") + import traceback + log(traceback.format_exc()) + +finally: + log("\n" + "="*70) + log("TEST COMPLETE") + log("="*70) + print(f"\nšŸ“„ Full log saved to: {log_file}") + print("Download this file to see all test results") diff --git a/app/test_deployment.py b/app/test_deployment.py new file mode 100644 index 0000000..5ef53f7 --- /dev/null +++ b/app/test_deployment.py @@ -0,0 +1,171 @@ +""" +Deployment diagnostic script for /product-finder +Run this on the server to verify configuration +""" +import os +import sys + +print("=" * 70) +print("CGW PRODUCT FINDER - DEPLOYMENT DIAGNOSTIC") +print("=" * 70) +print() + +# Check Python version +print("šŸ“Œ Python Environment:") +print(f" Version: {sys.version}") +print(f" Executable: {sys.executable}") +print(f" Current Directory: {os.getcwd()}") +print() + +# Check environment variable +print("šŸ“Œ Environment Variables:") +app_root_env = os.environ.get('APPLICATION_ROOT') +if app_root_env: + print(f" APPLICATION_ROOT: {app_root_env} āœ“") +else: + print(f" APPLICATION_ROOT: NOT SET āš ļø") + print(f" Setting temporarily for test: /product-finder") + os.environ['APPLICATION_ROOT'] = '/product-finder' +print() + +# Try importing Flask +print("šŸ“Œ Flask Installation:") +try: + import flask + print(f" Flask version: {flask.__version__} āœ“") +except ImportError as e: + print(f" āœ— Flask not installed: {e}") + sys.exit(1) + +# Try importing app +print() +print("šŸ“Œ Application Import:") +try: + from app import app + print(f" āœ“ App imported successfully") +except Exception as e: + print(f" āœ— Error importing app:") + print(f" {e}") + import traceback + traceback.print_exc() + sys.exit(1) + +# Check app configuration +print() +print("šŸ“Œ Application Configuration:") +print(f" APPLICATION_ROOT: {app.config.get('APPLICATION_ROOT')}") +print(f" SECRET_KEY set: {'Yes' if app.config.get('SECRET_KEY') else 'No'}") +print(f" DEBUG mode: {app.config.get('DEBUG')}") +print(f" ENV: {app.config.get('ENV', 'not set')}") +print() + +# Check middleware +print("šŸ“Œ WSGI Middleware:") +wsgi_app_type = type(app.wsgi_app).__name__ +if 'PrefixMiddleware' in str(type(app.wsgi_app)): + print(f" āœ“ PrefixMiddleware applied") + print(f" Type: {wsgi_app_type}") +elif wsgi_app_type == 'Flask': + print(f" āš ļø No middleware applied (running at root)") + print(f" Type: {wsgi_app_type}") +else: + print(f" Middleware type: {wsgi_app_type}") +print() + +# Check routes +print("šŸ“Œ Registered Routes:") +routes_count = len(list(app.url_map.iter_rules())) +print(f" Total routes: {routes_count}") + +# Check critical routes +critical_routes = ['/login', '/api/login', '/users', '/api/session'] +print(f" Critical routes:") +for route in critical_routes: + found = any(str(rule) == route for rule in app.url_map.iter_rules()) + status = "āœ“" if found else "āœ—" + print(f" {status} {route}") +print() + +# Check file structure +print("šŸ“Œ File Structure:") +critical_files = [ + 'app.py', + 'passenger_wsgi.py', + 'config.py', + 'templates/login.html', + 'templates/index2.html', + 'data/users.json' +] + +for file_path in critical_files: + exists = os.path.exists(file_path) + status = "āœ“" if exists else "āœ—" + print(f" {status} {file_path}") +print() + +# Check dependencies +print("šŸ“Œ Python Dependencies:") +deps = [ + ('werkzeug', 'Werkzeug'), + ('flask', 'Flask'), +] + +try: + from PIL import Image + deps.append(('PIL', 'Pillow')) +except ImportError: + pass + +for module_name, display_name in deps: + try: + module = __import__(module_name) + version = getattr(module, '__version__', 'unknown') + print(f" āœ“ {display_name}: {version}") + except ImportError: + print(f" āœ— {display_name}: NOT INSTALLED") +print() + +# Summary +print("=" * 70) +print("DEPLOYMENT STATUS") +print("=" * 70) + +issues = [] + +if not os.environ.get('APPLICATION_ROOT'): + issues.append("APPLICATION_ROOT not set in environment") + +if app.config.get('APPLICATION_ROOT') == '/': + issues.append("APPLICATION_ROOT is '/' but should be '/product-finder' for production") + +if not os.path.exists('templates/login.html'): + issues.append("Template files missing") + +if not os.path.exists('data/users.json'): + issues.append("User data file missing") + +if issues: + print() + print("āš ļø ISSUES FOUND:") + for issue in issues: + print(f" - {issue}") + print() + print("šŸ“ NEXT STEPS:") + print(" 1. Upload missing files to server") + print(" 2. Set APPLICATION_ROOT environment variable") + print(" 3. Restart Passenger: touch tmp/restart.txt") + print(" 4. Check error logs for details") +else: + print() + print("āœ“ All checks passed!") + print() + print("šŸš€ DEPLOYMENT READY") + print() + print("To restart Passenger:") + print(" mkdir -p tmp && touch tmp/restart.txt") + print() + print("To test:") + print(" https://columbiawindows.com/product-finder/") + +print() +print("=" * 70) diff --git a/app/test_routes.py b/app/test_routes.py new file mode 100644 index 0000000..96aa65d --- /dev/null +++ b/app/test_routes.py @@ -0,0 +1,86 @@ +""" +Quick test to verify Flask routes are registered correctly +Run this from the app folder: python test_routes.py +""" + +import sys +import os + +# Add current directory to path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +try: + from app import app + + print("=" * 60) + print("FLASK ROUTES TEST") + print("=" * 60) + print() + print("āœ“ Flask app imported successfully") + print() + print("Registered routes:") + print("-" * 60) + + routes = [] + for rule in app.url_map.iter_rules(): + routes.append({ + 'endpoint': rule.endpoint, + 'methods': ', '.join(sorted(rule.methods - {'HEAD', 'OPTIONS'})), + 'path': str(rule) + }) + + # Sort by path + routes.sort(key=lambda x: x['path']) + + # Find all login-related routes + login_routes = [r for r in routes if 'login' in r['path'].lower() or 'login' in r['endpoint'].lower()] + user_routes = [r for r in routes if 'user' in r['path'].lower()] + + print("\nšŸ“ Login & Authentication Routes:") + for route in login_routes: + print(f" {route['path']:40} [{route['methods']:15}] -> {route['endpoint']}") + + print("\nšŸ‘„ User Management Routes:") + for route in user_routes: + print(f" {route['path']:40} [{route['methods']:15}] -> {route['endpoint']}") + + print("\nšŸ“Š Total routes registered:", len(routes)) + print() + print("=" * 60) + print("āœ“ All routes loaded successfully!") + print("=" * 60) + print() + print("To start the server:") + print(" python app.py") + print() + print("Then access:") + print(" http://localhost:8080/login") + print(" http://localhost:8080/users") + print() + +except ImportError as e: + print("=" * 60) + print("āŒ IMPORT ERROR") + print("=" * 60) + print() + print(f"Failed to import Flask app: {e}") + print() + print("This might be because:") + print(" 1. Flask is not installed: pip install Flask") + print(" 2. Werkzeug is not installed: pip install Werkzeug") + print(" 3. There's a syntax error in app.py") + print() + print("Try running:") + print(" pip install -r requirements.txt") + print() + +except Exception as e: + print("=" * 60) + print("āŒ ERROR") + print("=" * 60) + print() + print(f"Error: {e}") + print() + import traceback + traceback.print_exc() + print() diff --git a/app/troubleshoot_server.py b/app/troubleshoot_server.py new file mode 100644 index 0000000..8707495 --- /dev/null +++ b/app/troubleshoot_server.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +Server troubleshooting script for 403/404 errors +Run this on the server to diagnose Apache/Passenger issues +""" +import os +import sys + +print("=" * 70) +print("TROUBLESHOOTING 403/404 ERRORS") +print("=" * 70) +print() + +# Check current directory +current_dir = os.getcwd() +print(f"šŸ“ Current directory: {current_dir}") +print() + +# Check file permissions +print("šŸ“Œ File Permissions Check:") +files_to_check = [ + 'passenger_wsgi.py', + '.htaccess', + 'app.py', + 'data/users.json', + 'templates/login.html' +] + +for file in files_to_check: + if os.path.exists(file): + stat_info = os.stat(file) + perms = oct(stat_info.st_mode)[-3:] + print(f" āœ“ {file:<30} Permissions: {perms}") + else: + print(f" āœ— {file:<30} NOT FOUND") +print() + +# Check directory permissions +print("šŸ“Œ Directory Permissions:") +dirs_to_check = ['.', 'templates', 'data', 'static', 'tmp'] +for dir_path in dirs_to_check: + if os.path.exists(dir_path): + stat_info = os.stat(dir_path) + perms = oct(stat_info.st_mode)[-3:] + print(f" āœ“ {dir_path:<30} Permissions: {perms}") + else: + print(f" āš ļø {dir_path:<30} NOT FOUND (might be OK)") +print() + +# Check for tmp/restart.txt +print("šŸ“Œ Passenger Restart Check:") +if os.path.exists('tmp/restart.txt'): + from datetime import datetime + mtime = os.path.getmtime('tmp/restart.txt') + mtime_str = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S') + print(f" āœ“ tmp/restart.txt exists (last modified: {mtime_str})") +else: + print(f" āœ— tmp/restart.txt NOT FOUND") + print(f" Create it with: mkdir -p tmp && touch tmp/restart.txt") +print() + +# Check environment variables +print("šŸ“Œ Environment Variables:") +print(f" APPLICATION_ROOT: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}") +print(f" HOME: {os.environ.get('HOME', 'NOT SET')}") +print(f" USER: {os.environ.get('USER', 'NOT SET')}") +print() + +# Check .htaccess content +print("šŸ“Œ .htaccess Configuration:") +if os.path.exists('.htaccess'): + print(f" āœ“ .htaccess exists") + with open('.htaccess', 'r') as f: + content = f.read() + if 'PassengerEnabled' in content: + print(f" āœ“ PassengerEnabled directive found") + else: + print(f" āœ— PassengerEnabled directive NOT FOUND") + + if 'PassengerAppRoot' in content: + print(f" āœ“ PassengerAppRoot directive found") + # Extract the path + for line in content.split('\n'): + if 'PassengerAppRoot' in line: + print(f" Value: {line.strip()}") + else: + print(f" āœ— PassengerAppRoot NOT FOUND") + + if 'PassengerPython' in content: + print(f" āœ“ PassengerPython directive found") + for line in content.split('\n'): + if 'PassengerPython' in line: + print(f" Value: {line.strip()}") + python_path = line.split()[-1] if len(line.split()) > 1 else '' + if os.path.exists(python_path): + print(f" āœ“ Python executable exists") + else: + print(f" āœ— Python executable NOT FOUND at {python_path}") +else: + print(f" āœ— .htaccess NOT FOUND") +print() + +# Try importing Flask +print("šŸ“Œ Flask Import Test:") +try: + sys.path.insert(0, current_dir) + from app import app + print(f" āœ“ Flask app imported successfully") + print(f" Routes registered: {len(list(app.url_map.iter_rules()))}") +except Exception as e: + print(f" āœ— Error importing app: {e}") +print() + +# Recommendations +print("=" * 70) +print("COMMON FIXES FOR 403/404 ERRORS:") +print("=" * 70) +print() +print("1ļøāƒ£ RESTART PASSENGER:") +print(" mkdir -p tmp") +print(" touch tmp/restart.txt") +print() +print("2ļøāƒ£ CHECK FILE PERMISSIONS:") +print(" chmod 644 passenger_wsgi.py") +print(" chmod 644 .htaccess") +print(" chmod 755 .") +print() +print("3ļøāƒ£ VERIFY PATHS IN .htaccess:") +print(" PassengerAppRoot should point to: " + current_dir) +print() +print("4ļøāƒ£ CHECK APACHE ERROR LOG:") +print(" tail -50 ~/logs/error_log") +print(" (Look for Passenger errors)") +print() +print("5ļøāƒ£ VERIFY .htaccess IS BEING READ:") +print(" If AllowOverride is not set in Apache config, .htaccess is ignored") +print() +print("6ļøāƒ£ SIMPLER .htaccess (if still failing):") +print(" Try creating a minimal .htaccess with just:") +print(" PassengerEnabled on") +print() +print("=" * 70) diff --git a/app/wsgi.py b/app/wsgi.py new file mode 100644 index 0000000..d8b19e3 --- /dev/null +++ b/app/wsgi.py @@ -0,0 +1,8 @@ +""" +WSGI entry point for production deployment +Use with Gunicorn: gunicorn wsgi:app +""" +from app import app + +if __name__ == "__main__": + app.run() diff --git a/app_simple/README.md b/app_simple/README.md new file mode 100644 index 0000000..fb2d35e --- /dev/null +++ b/app_simple/README.md @@ -0,0 +1,42 @@ +# Simple Flask Test App + +A minimal 2-page Flask application to test Passenger WSGI deployment. + +## Pages +- `/` - Home page (Hello World) +- `/about` - About page +- `/test` - Test page (shows system info) + +## Local Testing +```bash +cd app_simple +python app.py +``` +Visit: http://localhost:8080/ + +## Server Deployment + +### Upload these files to: `/home/bmdwtjuw/product-finder/` +- `app.py` +- `wsgi.py` +- `templates/home.html` +- `templates/about.html` + +### Control Panel Configuration +- **Application startup file:** `wsgi.py` +- **Application Entry point:** `app` +- **Python version:** 3.13.11 + +### Test URLs (after deployment) +- https://columbiawindows.com/product-finder/ +- https://columbiawindows.com/product-finder/about +- https://columbiawindows.com/product-finder/test + +## What This Tests +āœ“ Python 3.13.11 compatibility +āœ“ Flask framework loading +āœ“ Template rendering +āœ“ URL routing +āœ“ Passenger WSGI configuration + +Once this works, you can add back the login/user management features from `app2` folder. diff --git a/app_simple/app.py b/app_simple/app.py new file mode 100644 index 0000000..c2a108e --- /dev/null +++ b/app_simple/app.py @@ -0,0 +1,37 @@ +from flask import Flask, render_template + +# Initialize Flask +app = Flask(__name__) + +@app.route('/') +def home(): + """Home page - Hello World""" + return render_template('home.html') + +@app.route('/about') +def about(): + """About page""" + return render_template('about.html') + +@app.route('/test') +def test(): + """Test route - no template needed""" + import sys + return f""" + + Test Page + +

Flask App is Running!

+
    +
  • Python: {sys.version}
  • +
  • Flask: Working
  • +
  • Routes: {len(list(app.url_map.iter_rules()))}
  • +
+

Home | About

+ + + """ + +if __name__ == '__main__': + # Run locally on port 8080 + app.run(debug=True, host='0.0.0.0', port=8080) diff --git a/app_simple/check_deployment.py b/app_simple/check_deployment.py new file mode 100644 index 0000000..e9cbbb7 --- /dev/null +++ b/app_simple/check_deployment.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Deployment Check Script +Run this on the server to verify configuration +""" +import os +import sys + +print("=" * 70) +print("DEPLOYMENT CONFIGURATION CHECK") +print("=" * 70) + +# Check Python version +print(f"\nPython Version: {sys.version}") +print(f"Python Executable: {sys.executable}") + +# Check current directory +print(f"\nCurrent Directory: {os.getcwd()}") + +# Check for required files +print("\n" + "=" * 70) +print("CHECKING FILES") +print("=" * 70) + +files_to_check = ['app.py', 'wsgi.py', 'templates/home.html', 'templates/about.html'] +for file in files_to_check: + exists = "āœ“ EXISTS" if os.path.exists(file) else "āœ— MISSING" + print(f"{exists}: {file}") + +# Check for passenger_wsgi.py (should NOT exist) +if os.path.exists('passenger_wsgi.py'): + print("āœ— WARNING: passenger_wsgi.py exists - DELETE THIS FILE!") +else: + print("āœ“ GOOD: passenger_wsgi.py does not exist") + +# Try importing Flask +print("\n" + "=" * 70) +print("CHECKING FLASK") +print("=" * 70) +try: + import flask + print(f"āœ“ Flask version: {flask.__version__}") +except ImportError as e: + print(f"āœ— Flask not installed: {e}") + +# Try importing the app +print("\n" + "=" * 70) +print("CHECKING APP IMPORT") +print("=" * 70) +try: + from app import app + print("āœ“ App imported successfully") + print(f"āœ“ App name: {app.name}") + print(f"āœ“ Routes count: {len(list(app.url_map.iter_rules()))}") +except Exception as e: + print(f"āœ— Error importing app: {e}") + import traceback + traceback.print_exc() + +print("\n" + "=" * 70) +print("DONE") +print("=" * 70) diff --git a/app_simple/requirements.txt b/app_simple/requirements.txt new file mode 100644 index 0000000..f31cf55 --- /dev/null +++ b/app_simple/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.0.0 +Werkzeug==3.0.1 diff --git a/app_simple/templates/about.html b/app_simple/templates/about.html new file mode 100644 index 0000000..9248f53 --- /dev/null +++ b/app_simple/templates/about.html @@ -0,0 +1,62 @@ + + + + + + About - Simple Flask App + + + +
+

About This App

+

This is a simple 2-page Flask application created to test server deployment configuration.

+

Purpose: Verify that Passenger WSGI configuration works correctly.

+ +

Configuration:

+
    +
  • Startup file: wsgi.py
  • +
  • Entry point: app
  • +
  • Framework: Flask 3.0.0
  • +
  • Python: 3.13.11
  • +
+ + +
+ + diff --git a/app_simple/templates/home.html b/app_simple/templates/home.html new file mode 100644 index 0000000..35f1690 --- /dev/null +++ b/app_simple/templates/home.html @@ -0,0 +1,54 @@ + + + + + + Home - Simple Flask App + + + +
+

Hello World!

+

Welcome to the simple Flask application.

+

This is a minimal test to verify the server configuration works correctly.

+ + +
+ + diff --git a/app_simple/wsgi.py b/app_simple/wsgi.py new file mode 100644 index 0000000..3c50ab1 --- /dev/null +++ b/app_simple/wsgi.py @@ -0,0 +1,4 @@ +from app import app + +if __name__ == "__main__": + app.run() diff --git a/information/AI_PARSING_INSTRUCTIONS.md b/information/AI_PARSING_INSTRUCTIONS.md new file mode 100644 index 0000000..c25b5ce --- /dev/null +++ b/information/AI_PARSING_INSTRUCTIONS.md @@ -0,0 +1,723 @@ +# AI Data Parsing Instructions + +## šŸ“‹ Quick Reference + +**Input**: `data/products.csv` (1000 rows with product and accessory data) + +**Outputs**: +1. `data/navigation.json` - Smart navigation flow with conditional questions +2. `data/products.json` - Product catalog with materials, colors, and accessory links +3. `data/accessories.json` - Accessory options with compatibility rules + +**Key Features**: +- āœ… Conditional material questions (only show if multiple materials exist in sub-type) +- āœ… Conditional color questions (only show if multiple colors available) +- āœ… Auto-skip questions when only one option exists +- āœ… Smart product-accessory linking by sub-type, category, or specific product code +- šŸ”„ Multi-group assignment using bit values (optional enhancement) + +--- + +## Overview +Parse the `data/products.csv` file to generate three separate JSON files that structure product data, navigation, and accessory options for a doors and windows e-commerce application. + +## Source File Structure + +### CSV File: `data/products.csv` +The CSV contains product and accessory data with the following column structure: + +**Row 1**: Category headers (informational) +**Row 2**: Column names (actual headers) + +### Column Mapping (0-indexed): +- **A (0)**: DISCONT - Discontinued flag (TRUE/FALSE) +- **B (1)**: LOC_CODE - Location code (e.g., "Iola") +- **C (2)**: PROD_CODE - Product code (unique identifier) +- **D (3)**: CATEGORY - Category code +- **E (4)**: CATEGORY_NEW - New category (usually empty) +- **F (5)**: DESCRIPTION - Full product description +- **G (6)**: Base Type - Primary type ("Window", "Door", or empty) +- **H (7)**: Sub-type Door - Door subcategory (e.g., "Storm Door") +- **I (8)**: Sub-type Window - Window subcategory (e.g., "Storm Window") +- **J (9)**: Accessory Yes - Boolean indicating if item is an accessory +- **K (10)**: This Item - Boolean for specific item relationship +- **L+ (11+)**: Material and Color availability (Aluminum, Vinyl, Black, White, Bronze, Tan, Mill, Sandstone, etc.) + +--- + +## Output File 1: `data/navigation.json` + +### Purpose +Generate a navigation flow with questions and button options to guide users through product selection. + +### ⚔ Key Navigation Principles + +1. **Question 1 (Start)**: Built from Column G (Base Type) - Door, Window, etc. +2. **Question 2 (Sub-Type)**: Built from Column H (Doors) OR Column I (Windows) based on Q1 selection +3. **Question 3 (Material)**: **CONDITIONAL** - Only show if sub-type has products with multiple materials (Aluminum AND Vinyl) + - Example: Storm Doors (all Aluminum) → SKIP this question + - Example: Mixed Windows (some Aluminum, some Vinyl) → SHOW this question +4. **Question 4 (Color)**: **CONDITIONAL** - Only show if filtered products have multiple color options +5. **Question 5 (Dimensions)**: Always show - final step before product display +6. **Dynamic Linking**: The "next" property must skip questions that aren't needed for that path + +### Structure +```json +{ + "start": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "answers": [ + { + "caption": "PRODUCT_TYPE", + "image": "EMOJI", + "next": "NEXT_QUESTION_ID", + "filter": { + "baseType": "VALUE" + } + } + ] + }, + "q-QUESTION_ID": { + "type": "question", + "inputType": "button|form", + "title": "Question Title", + "subtitle": "Question subtitle", + "answers": [...], + "fields": [...] + } +} +``` + +### Generation Rules + +#### Question 1: Base Type Selection (Start) +**Source**: Column G (Base Type) +1. Extract all unique values from column G where DISCONT=FALSE +2. Create button option for each unique base type (e.g., "Window", "Door") +3. Assign appropriate emojis (🪟 for windows, 🚪 for doors) +4. Each answer links to the corresponding sub-type question + +**Example**: "What are you looking for?" → Window / Door options + +#### Question 2: Sub-Type Selection +**Source**: Column H (for Doors) OR Column I (for Windows) +1. **For Door selection**: Use column H (Sub-type Door) + - Extract unique values where column G = "Door" and DISCONT=FALSE + - Examples: "Storm Door", "Patio Door", etc. + +2. **For Window selection**: Use column I (Sub-type Window) + - Extract unique values where column G = "Window" and DISCONT=FALSE + - Examples: "Storm Window", "Casement", etc. + +3. Create separate question branches: + - `q-door-type`: For door sub-types + - `q-window-type`: For window sub-types + +4. Each answer links to either material question (if needed) or dimensions question + +**Example**: "What type of door?" → Storm Door / Patio Door options + +#### Question 3: Material Selection (CONDITIONAL) +**Source**: Columns L (Aluminum) and M (Vinyl) +**Important**: This question should **only appear** if products in the selected sub-type have multiple material options. + +**Logic**: +1. For each sub-type group, count materials: + - Count products where Aluminum (column L) = TRUE + - Count products where Vinyl (column M) = TRUE + +2. **Show material question** if: + - Some products have Aluminum=TRUE AND some have Vinyl=TRUE + - OR any single product has BOTH Aluminum=TRUE AND Vinyl=TRUE + +3. **Skip material question** if: + - ALL products in the sub-type have only one material type + - Example: All "Storm Door" products only have Aluminum=TRUE → Skip material question + +4. If shown, create radio buttons or select dropdown with: + - Only materials that exist in the sub-type group + - Link to color question or dimensions + +**Example Skip Case**: Storm Doors (all Aluminum only) → Skip directly to dimensions +**Example Show Case**: Windows (some Aluminum, some Vinyl) → Show material selection + +#### Question 4: Color Selection (CONDITIONAL) +**Source**: Color columns (N through S+) +Similar conditional logic as materials: +1. Only show if products in the filtered group have multiple color options +2. Present only colors available for the selected material (if material was selected) +3. Skip if all products have the same single color + +#### Question 5: Dimensions Entry (Always Show) +**Source**: User input +1. Width input field (number, required) +2. Height input field (number, required) +3. This is typically the final question before showing filtered products + +#### Filter Integration +Each answer should accumulate filter criteria: +```json +"filter": { + "baseType": "Window|Door", + "subType": "Storm Door|etc", + "material": "Aluminum|Vinyl", // Only if material question was shown + "color": "White|Bronze|etc", // Only if color question was shown + "category": "CATEGORY_CODE" +} +``` + +#### Navigation Flow Schema +``` +start (Column G) + → q-door-type (Column H) OR q-window-type (Column I) + → q-material (Columns L,M) [CONDITIONAL - only if multiple materials exist] + → q-color (Columns N+) [CONDITIONAL - only if multiple colors exist] + → q-dimensions (User Input) + → results (Filtered products) +``` + +**Dynamic Linking**: The "next" value for each question must be calculated based on whether the next conditional question is needed: +- If material question not needed → link sub-type directly to color or dimensions +- If color question not needed → link material (or sub-type) directly to dimensions + +--- + +## Output File 2: `data/products.json` + +### Purpose +Store all product data for display on product detail pages. + +### Structure +```json +[ + { + "id": "PROD_CODE", + "productCode": "PROD_CODE", + "category": "CATEGORY", + "description": "DESCRIPTION", + "discontinued": false, + "location": "LOC_CODE", + "baseType": "Window|Door", + "subType": { + "door": "VALUE_OR_NULL", + "window": "VALUE_OR_NULL" + }, + "materials": ["Aluminum", "Vinyl"], + "colors": ["Black", "White", "Bronze", "Tan", "Mill", "Sandstone"], + "isAccessory": false, + "compatibleAccessories": ["PROD_CODE1", "PROD_CODE2"] + } +] +``` + +### Generation Rules +1. **Include all rows** where `DISCONT` (column A) is FALSE +2. **Skip accessories** (column J = TRUE) - those go in accessories.json +3. **Materials array**: Include all material columns (L+) where value is TRUE +4. **Colors array**: Include all color columns where value is TRUE +5. **baseType**: Copy from column G +6. **subType**: Create object with "door" and "window" keys from columns H and I +7. **compatibleAccessories**: Populate based on accessories linked to this product (see Accessories section) + +--- + +## Output File 3: `data/accessories.json` + +### Purpose +Store accessory/option data that can be applied to products (e.g., handles for storm doors, glass inserts). + +### Structure +```json +[ + { + "id": "PROD_CODE", + "accessoryCode": "PROD_CODE", + "category": "CATEGORY", + "description": "DESCRIPTION", + "discontinued": false, + "location": "LOC_CODE", + "baseType": "Window|Door", + "materials": ["Aluminum"], + "colors": ["Black", "White"], + "compatibilityRules": { + "type": "subType|category|specific", + "subTypeDoor": "Storm Door", + "subTypeWindow": null, + "categories": ["STD", "INSERTS"], + "specificProducts": ["6100I", "8100I"], + "requiresMatch": { + "material": true, + "color": false + } + }, + "optionType": "handle|insert|hardware|glass", + "metadata": { + "specificItemLink": false + } + } +] +``` + +### Generation Rules +1. **Include all rows** where `Accessory Yes` (column J) is TRUE +2. **Skip discontinued** items (column A = TRUE) +3. **compatibilityRules.type**: Determine based on available data + - If column H (Sub-type Door) or column I (Sub-type Window) has value → "subType" + - If column K (This Item) is TRUE → "specific" (requires product linking) + - Otherwise → "category" + +4. **compatibilityRules.subTypeDoor/Window**: Copy from columns H and I +5. **compatibilityRules.categories**: Extract from column D (CATEGORY) +6. **compatibilityRules.specificProducts**: To be populated based on column K logic + - If column K is TRUE, this accessory is for specific products + - You may need to analyze patterns in PROD_CODE or CATEGORY to determine relationships + - Example: "BGIST" (inserts for storm doors) might be compatible with all storm door products + +7. **optionType**: Infer from DESCRIPTION or CATEGORY + - If DESCRIPTION contains "HANDLE" → "handle" + - If DESCRIPTION contains "INSERT" → "insert" + - If DESCRIPTION contains "HARDWARE" → "hardware" + - If DESCRIPTION contains "GLASS" → "glass" + - Default → "option" + +8. **requiresMatch**: Set based on whether accessory materials/colors must match product + - For handles/hardware: material matching often required + - For inserts: usually flexible + +--- + +## Multi-Group Assignment (Bit Values Consideration) + +### Current Structure +Currently, each product belongs to a single category/sub-type based on columns G, H, and I. + +### Proposed Enhancement: Bit Flags +To allow products to appear in multiple navigation groups, consider implementing bit flags for categories: + +```json +{ + "productCode": "EXAMPLE", + "description": "Multi-purpose product", + "categoryFlags": 7, // Binary: 111 (belongs to groups 1, 2, and 4) + "categoryBits": { + "residential": 1, // 2^0 = 1 + "commercial": 2, // 2^1 = 2 + "industrial": 4, // 2^2 = 4 + "custom": 8 // 2^3 = 8 + }, + "navigationGroups": ["residential", "commercial", "industrial"] +} +``` + +### Implementation Options + +#### Option A: Additional CSV Columns +Add bit value columns to track multiple group memberships: +- Column U: Navigation Group Bits (integer) +- Column V: Secondary Category +- Column W: Tertiary Category + +#### Option B: Parse from Description/Category +Analyze DESCRIPTION and CATEGORY fields to identify products that could belong to multiple groups: +- Keywords indicating dual-purpose (e.g., "residential/commercial") +- Multiple category codes separated by delimiter + +#### Option C: JSON-Only Enhancement +Generate single-group assignments from CSV, then manually or programmatically enhance JSON with additional group memberships based on business rules. + +### Navigation Impact +With bit values: +1. Multiple sub-type paths could lead to the same product +2. Products appear in search results for multiple filter combinations +3. Requires modification to filter logic to use bitwise operations + +**Example**: A storm door that works for both residential and commercial applications could appear in both navigation paths. + +### Refinement Needed +This feature requires: +- āœ… Business rules for multi-group assignment +- āœ… Decision on implementation approach (CSV vs JSON) +- āœ… Filter logic updates to handle bitwise comparisons +- āœ… Testing to ensure products appear in correct groups +- āœ… UI considerations (showing product appears in multiple categories) + +--- + +## Data Relationships + +### Products ↔ Accessories Linking + +1. **By Sub-Type**: Accessories with matching sub-type values (columns H or I) are compatible + - Example: Accessory with subTypeDoor="Storm Door" → compatible with all products where subType.door="Storm Door" + +2. **By Category**: Accessories linked to specific CATEGORY codes + - Example: Accessory with category="INSERTS" might be compatible with products in "STD" category + +3. **By Specific Product Code**: Use column K (This Item) flag + - If TRUE, requires manual mapping or pattern analysis + - Consider adding a "specificProductCodes" field to link directly + +### Recommendation Algorithm +When displaying accessories for a product: +``` +1. Match by specificProducts first (exact match) +2. Match by subType (door or window) +3. Match by category +4. Filter by material/color compatibility if requiresMatch is true +5. Exclude discontinued accessories +``` + +--- + +## Processing Steps + +### Step 1: Parse CSV +1. Read products.csv starting from row 3 (skip header rows 1-2) +2. Split each row by comma delimiter +3. Handle empty fields appropriately +4. Parse boolean values (TRUE/FALSE → true/false in JSON) + +### Step 2: Categorize Rows +1. Separate products (column J = FALSE) from accessories (column J = TRUE) +2. Filter out discontinued items (column A = TRUE) or include with flag + +### Step 3: Extract Navigation Data +1. Collect unique values from columns G, H, I +2. For each sub-type group, analyze material and color diversity +3. Build question hierarchy with conditional logic: + - Level 1: Base Type (Window, Door) - from column G + - Level 2: Sub-Type (Storm Door, Casement, etc.) - from columns H/I + - Level 3: Material (CONDITIONAL) - from columns L, M + - Level 4: Color (CONDITIONAL) - from color columns + - Level 5: Dimensions & final inputs +4. Generate question flow with proper linking (next values) + +#### Algorithm: Determine Material Question Necessity +``` +For each sub-type group: + products = filter products where subType matches AND DISCONT=FALSE + + aluminumCount = count products where Aluminum (col L) = TRUE + vinylCount = count products where Vinyl (col M) = TRUE + bothCount = count products where Aluminum=TRUE AND Vinyl=TRUE + + IF (aluminumCount > 0 AND vinylCount > 0) OR bothCount > 0: + SHOW material question for this sub-type + Create q-material-{subtype} with options for available materials + ELSE: + SKIP material question + Link sub-type answer directly to color question or dimensions + Auto-apply the single material to filter +``` + +#### Algorithm: Determine Color Question Necessity +``` +For each (sub-type, material) combination: + products = filter products where match AND DISCONT=FALSE + + availableColors = [] + For each color column (N through S+): + IF any product has this color = TRUE: + add color to availableColors + + IF len(availableColors) > 1: + SHOW color question for this path + ELSE IF len(availableColors) = 1: + SKIP color question + Auto-apply the single color to filter + ELSE: + SKIP color question (no color data) +``` + +### Step 4: Build Products Array +1. For each non-accessory, non-discontinued row: + - Extract all product fields + - Parse material/color columns into arrays + - Create product object + - Add to products array + +### Step 5: Build Accessories Array +1. For each accessory row: + - Extract accessory fields + - Determine compatibility rules + - Infer option type from description + - Create accessory object + - Add to accessories array + +### Step 6: Link Products to Accessories +1. For each product, find compatible accessories based on: + - Sub-type matching + - Category matching + - Specific product code matching +2. Populate compatibleAccessories array in products.json +3. Verify bidirectional relationships + +### Step 7: Validate Output +1. Ensure all JSON is valid and properly formatted +2. Check that all question flows have valid "next" links +3. Verify product-accessory relationships are logical +4. Confirm no duplicate IDs exist + +--- + +## Advanced Considerations + +### Column Extensions +If additional columns are added beyond column T (~): +- Check for additional material/color flags +- Look for price, availability, or specification data +- Include in metadata or as new product properties + +### Future Enhancements +You may want to extend the schema with: +1. **Pricing**: Add price fields to products and accessories +2. **Images**: Add image URLs or filenames +3. **Specifications**: Add detailed specs (dimensions, ratings, etc.) +4. **Availability**: Add stock levels or lead times +5. **Sorting**: Add sort order or priority fields + +--- + +## Example Outputs + +### Example Product Object +```json +{ + "id": "6100I", + "productCode": "6100I", + "category": "STD", + "description": "STAR 6100 FULL VIEW STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": ["Aluminum"], + "colors": ["White", "Bronze"], + "isAccessory": false, + "compatibleAccessories": ["BGIST", "TGIODD"] +} +``` + +### Example Accessory Object +```json +{ + "id": "BGIST", + "accessoryCode": "BGIST", + "category": "INSERTS", + "description": "INSERTS FOR STORM DOORS", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "materials": ["Aluminum"], + "colors": ["Black", "White", "Bronze", "Mill", "Sandstone"], + "compatibilityRules": { + "type": "subType", + "subTypeDoor": "Storm Door", + "subTypeWindow": null, + "categories": ["STD", "PD"], + "specificProducts": [], + "requiresMatch": { + "material": true, + "color": false + } + }, + "optionType": "insert", + "metadata": { + "specificItemLink": false + } +} +``` + +### Example Navigation Flow +```json +{ + "start": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "answers": [ + { + "caption": "Door", + "image": "🚪", + "next": "q-door-type", + "filter": { "baseType": "Door" } + }, + { + "caption": "Window", + "image": "🪟", + "next": "q-window-type", + "filter": { "baseType": "Window" } + } + ] + }, + "q-door-type": { + "type": "question", + "inputType": "button", + "title": "What type of door?", + "subtitle": "Select the door category", + "answers": [ + { + "caption": "Storm Door", + "image": "🚪", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Storm Door", + "material": "Aluminum" + }, + "note": "Material question skipped - all storm doors are aluminum only" + }, + { + "caption": "Patio Door", + "image": "🚪", + "next": "q-material-patio", + "filter": { + "baseType": "Door", + "subType": "Patio Door" + }, + "note": "Material question shown - patio doors have multiple material options" + } + ] + }, + "q-material-patio": { + "type": "question", + "inputType": "button", + "title": "Select Material", + "subtitle": "Choose your preferred material for Patio Door", + "answers": [ + { + "caption": "Aluminum", + "image": "šŸ”©", + "next": "q-color-patio-aluminum", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum" + } + }, + { + "caption": "Vinyl", + "image": "🪟", + "next": "q-color-patio-vinyl", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Vinyl" + } + } + ], + "conditional": { + "showIf": "multipleOptionsExist", + "check": "materials", + "fallbackNext": "q-dimensions" + } + }, + "q-color-patio-aluminum": { + "type": "question", + "inputType": "button", + "title": "Select Color", + "subtitle": "Choose your preferred color", + "answers": [ + { + "caption": "White", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum", + "color": "White" + } + }, + { + "caption": "Bronze", + "next": "q-dimensions", + "filter": { + "baseType": "Door", + "subType": "Patio Door", + "material": "Aluminum", + "color": "Bronze" + } + } + ] + }, + "q-dimensions": { + "type": "question", + "inputType": "form", + "title": "Enter Product Dimensions", + "subtitle": "Please provide the measurements", + "fields": [ + { + "name": "width", + "label": "Width (inches)", + "type": "number", + "required": true, + "placeholder": "e.g., 36" + }, + { + "name": "height", + "label": "Height (inches)", + "type": "number", + "required": true, + "placeholder": "e.g., 80" + } + ], + "next": "results" + } +} +``` + +**Key Points in Example**: +- Storm Door skips material question (goes directly to dimensions) +- Patio Door shows material question (multiple materials available) +- Material selection leads to color-specific questions +- All paths eventually reach dimensions entry + +--- + +## Final Notes + +1. **Data Quality**: Some rows may have inconsistent data. Handle gracefully with defaults. +2. **Empty Values**: Treat empty strings as null in JSON +3. **Boolean Conversion**: CSV TRUE/FALSE should become JSON true/false +4. **Unique IDs**: PROD_CODE serves as the unique identifier +5. **Relationships**: The linking between products and accessories may require iterative refinement based on business rules + +## Questions to Consider + +When implementing, clarify: +1. Should discontinued items be included in ANY output file? +2. How should accessories with column K=TRUE be specifically linked? +3. Are there additional columns beyond column S that need parsing? +4. Should colors and materials be validated against a master list? +5. What should happen if a product has no compatible accessories? +6. **Should the navigation include filters for materials/colors early, or determine dynamically based on product availability?** āœ… RESOLVED: Dynamic based on availability +7. **For sub-types with only one material option, should that material be auto-applied to the filter?** → YES, skip question and auto-apply +8. **Multi-group assignment (bit values):** + - Should products be able to appear in multiple navigation paths? + - If yes, how should this be indicated in the CSV? (new column, description parsing, manual JSON editing?) + - What business rules determine multi-group membership? + - Should search results indicate a product appears in multiple categories? +9. **Material/Color question threshold:** + - Current logic: Show if > 1 option exists + - Alternative: Show only if > X% of products have multiple options (e.g., 20% threshold) + - Should we show questions even if only 1-2 products have alternative options? + +--- + +## Success Criteria + +The parsing is complete when: +- āœ… All three JSON files are generated and valid +- āœ… Products.json contains all non-accessory, non-discontinued products +- āœ… Accessories.json contains all accessory items with proper compatibility rules +- āœ… Navigation.json provides a complete question flow from start to product selection +- āœ… Product-accessory relationships are established and logical +- āœ… All materials and colors are properly extracted into arrays +- āœ… No data loss from the original CSV diff --git a/information/BITWISE_USAGE_GUIDE.md b/information/BITWISE_USAGE_GUIDE.md new file mode 100644 index 0000000..f698b8e --- /dev/null +++ b/information/BITWISE_USAGE_GUIDE.md @@ -0,0 +1,221 @@ +# Bitwise Helper Files - Usage Guide + +## šŸ“ Generated Files + +1. **`data/product_bitwise.csv`** - CSV format with bit values for each product +2. **`data/product_bitwise.json`** - JSON format with bit values for each product +3. **`data/bitwise_legend.json`** - Complete bit mapping documentation + +## šŸ”¢ Bit System Overview + +Each product gets a single integer value that encodes multiple attributes using bitwise flags. + +### Bit Positions (0-11): Base Attributes, Materials, Colors + +| Bit | Value | Attribute | +|-----|-------|-----------| +| 0 | 1 | Door | +| 1 | 2 | Window | +| 2 | 4 | Is Accessory | +| 3 | 8 | Specific Item Link | +| 4 | 16 | Aluminum Material | +| 5 | 32 | Vinyl Material | +| 6 | 64 | Black Color | +| 7 | 128 | White Color | +| 8 | 256 | Bronze Color | +| 9 | 512 | Tan Color | +| 10 | 1024 | Mill Color | +| 11 | 2048 | Sandstone Color | + +### Bit Positions (12+): Sub-types + +| Bit | Value | Sub-type | +|-----|-------|----------| +| 12 | 4096 | Patio Door | +| 13 | 8192 | Primary Window | +| 14 | 16384 | Storm Door | +| 15 | 32768 | Storm Window | + +## šŸ’” Usage Examples + +### Example 1: Storm Door Insert (BGIST) +``` +Product Code: BGIST +Description: INSERTS FOR STORM DOORS +Bit Value: 17553 +Binary: 0100010010010001 + +Decoded: + āœ“ Door (bit 0 = 1) + āœ— Window (bit 1 = 0) + āœ— Accessory (bit 2 = 0) + āœ— Specific Item (bit 3 = 0) + āœ“ Aluminum (bit 4 = 1) + āœ— Vinyl (bit 5 = 0) + āœ“ Black (bit 6 = 1) + āœ“ White (bit 7 = 1) + āœ“ Bronze (bit 8 = 1) + āœ— Tan (bit 9 = 0) + āœ“ Mill (bit 10 = 1) + āœ“ Sandstone (bit 11 = 1) + āœ— Patio Door (bit 12 = 0) + āœ— Primary Window (bit 13 = 0) + āœ“ Storm Door (bit 14 = 1) + āœ— Storm Window (bit 15 = 0) +``` + +### Example 2: Check Attributes in Code + +#### Python +```python +import json + +# Load helper file +with open('data/product_bitwise.json') as f: + products = json.load(f) + +# Get a product +product = next(p for p in products if p['PROD_CODE'] == 'BGIST') +bit_value = product['BIT_VALUE'] + +# Check individual flags +is_door = bool(bit_value & 1) +is_aluminum = bool(bit_value & 16) +is_white = bool(bit_value & 128) +is_storm_door = bool(bit_value & 16384) + +print(f"BGIST is door: {is_door}") +print(f"BGIST is aluminum: {is_aluminum}") +print(f"BGIST is white: {is_white}") +print(f"BGIST is storm door subtype: {is_storm_door}") + +# Filter products by multiple criteria +# Example: Find all aluminum doors with white color +aluminum_white_doors = [ + p for p in products + if (p['BIT_VALUE'] & 1) and # Is a door + (p['BIT_VALUE'] & 16) and # Has aluminum + (p['BIT_VALUE'] & 128) and # Has white + not p['DISCONTINUED'] # Not discontinued +] + +print(f"Found {len(aluminum_white_doors)} aluminum white doors") +``` + +#### JavaScript +```javascript +// Load the JSON file +fetch('data/product_bitwise.json') + .then(response => response.json()) + .then(products => { + // Check individual flags + const product = products.find(p => p.PROD_CODE === 'BGIST'); + const bitValue = product.BIT_VALUE; + + const isDoor = !!(bitValue & 1); + const isAluminum = !!(bitValue & 16); + const isWhite = !!(bitValue & 128); + const isStormDoor = !!(bitValue & 16384); + + console.log(`BGIST is door: ${isDoor}`); + console.log(`BGIST is aluminum: ${isAluminum}`); + console.log(`BGIST is white: ${isWhite}`); + console.log(`BGIST is storm door: ${isStormDoor}`); + + // Filter products + const aluminumWhiteDoors = products.filter(p => + (p.BIT_VALUE & 1) && // Is a door + (p.BIT_VALUE & 16) && // Has aluminum + (p.BIT_VALUE & 128) && // Has white + !p.DISCONTINUED // Not discontinued + ); + + console.log(`Found ${aluminumWhiteDoors.length} aluminum white doors`); + }); +``` + +## šŸŽÆ Benefits of Bitwise Classification + +### 1. **Multi-Group Assignment** +Products can belong to multiple categories simultaneously: +- A product can be both Door AND Window (rare but possible) +- A product can have multiple materials (Aluminum AND Vinyl) +- A product can have multiple colors + +### 2. **Fast Filtering** +Bitwise operations are extremely fast: +```python +# Instead of: +if product.baseType == 'Door' and 'Aluminum' in product.materials and 'White' in product.colors: + +# Use: +if (bit_value & 1) and (bit_value & 16) and (bit_value & 128): +``` + +### 3. **Compact Storage** +One integer stores multiple attributes: +- Single integer vs. multiple boolean fields +- Easy to index and search in databases +- Efficient for large datasets + +### 4. **Easy Matching** +Perfect for accessory compatibility: +```python +# Check if accessory matches product materials +accessory_materials = 48 # Aluminum (16) + Vinyl (32) +product_materials = 16 # Aluminum only + +# Check if they share any materials +if accessory_materials & product_materials: + print("Compatible!") # True because both have Aluminum +``` + +## šŸ”„ Integration with Navigation System + +Use bitwise values to: +1. Dynamically generate navigation options +2. Filter products in real-time based on user selections +3. Match accessories to products efficiently +4. Handle complex "OR" queries (multiple categories) + +### Example: Dynamic Material Question +```python +# Get all products for "Storm Door" subtype +storm_door_products = [p for p in products if p['BIT_VALUE'] & 16384] + +# Check materials +has_aluminum = any(p['BIT_VALUE'] & 16 for p in storm_door_products) +has_vinyl = any(p['BIT_VALUE'] & 32 for p in storm_door_products) + +# Show material question only if multiple materials exist +if has_aluminum and has_vinyl: + show_material_question() +else: + skip_to_next_question() +``` + +## šŸ“ Maintenance + +### Regenerating Helper Files +When products.csv changes: +```bash +python generate_bitwise_helper.py +``` + +### Adding New Attributes +To add new bit flags, edit `generate_bitwise_helper.py`: +1. Add to `BIT_DEFINITIONS` dictionary +2. Update `calculate_bit_value()` function +3. Regenerate files + +### Adding New Sub-types +Sub-types are automatically detected! Just add them to columns H or I in the CSV, then regenerate. + +## šŸš€ Performance Notes + +- Bitwise AND (`&`) - Check if flag is set: `bit_value & 16` +- Bitwise OR (`|`) - Combine flags: `16 | 128` = Aluminum + White +- Bitwise NOT (`~`) - Invert flags (advanced) +- Bitwise XOR (`^`) - Toggle flags (advanced) + +All bitwise operations are O(1) - constant time! diff --git a/information/DEPLOYMENT_GUIDE.md b/information/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..a739b69 --- /dev/null +++ b/information/DEPLOYMENT_GUIDE.md @@ -0,0 +1,238 @@ +# Deployment Guide - Files to Upload to Server + +## šŸš€ Updated Files for Login System & Permission System + +To deploy the new login, location selection, user management, and permission system to your server, you'll need to upload the following files: + +### āœ… Essential Updated Files + +#### **1. Main Application Files** +These core files have been modified and MUST be uploaded: +- `app/app.py` - Main Flask application with authentication, sessions, and permissions +- `app/config.py` - Configuration (verify SECRET_KEY is set) +- `app/requirements.txt` - Python dependencies (may need to run `pip install -r requirements.txt` on server) + +#### **2. HTML Templates** (all in `app/templates/`) +- `app/templates/login.html` - Login page +- `app/templates/select_location.html` - Location selection page (with User Management link) +- `app/templates/user_manager.html` - User management interface (with password change) +- `app/templates/index2.html` - Main app with user info header +- `app/templates/access_denied.html` - Permission denied page + +#### **3. CSS Files** +- `app/css/styles.css` - Updated styles for user-info-bar and logout button + +#### **4. User Data** +- `app/data/users.json` - User accounts with permissions +- `app/data/example_user_structure.json` - Example data format (documentation only) + +āš ļø **Important**: If you have existing users on the server, back them up first, then merge the permission structure into existing user accounts. + +### šŸ“ New Folders/Files Created + +#### **Admin Utilities** (optional, but recommended) +- `app/admin/` - New folder +- `app/admin/README.md` - Admin utilities documentation +- `app/admin/fix_default_locations.py` - User maintenance tool +- `app/admin/test_password_security.py` - Password security demo + +āš ļø **Security Note**: The `app/admin/` folder should NOT be web-accessible. Configure your server to block access to this directory. + +### šŸ“š Documentation Files (moved to information/) +These files are for reference only and do NOT need to be uploaded to the production server: +- `information/LOGIN_SYSTEM_README.md` +- `information/PERMISSIONS_SYSTEM.md` +- `information/PERMISSIONS_QUICKSTART.md` +- `information/USER_MANAGEMENT_README.md` + +### šŸ”§ Server Configuration + +#### **Python Dependencies** +After uploading files, install/update dependencies on the server: +```bash +cd /path/to/app +pip install -r requirements.txt +``` + +**Key dependencies** (will be installed from requirements.txt): +- Flask >= 3.0.0 +- Werkzeug (for password hashing) + +#### **Secret Key Configuration** +Ensure `app/config.py` has a strong SECRET_KEY: +```python +SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-production-secret-key-here' +``` + +For production, use environment variable or generate with: +```python +import secrets +print(secrets.token_urlsafe(32)) +``` + +#### **File Permissions** +Set appropriate permissions on the server: +```bash +# Data directory should be writable by the web server +chmod 755 app/data/ +chmod 644 app/data/users.json + +# Admin directory should NOT be web accessible +chmod 700 app/admin/ +``` + +#### **WSGI Configuration** +If using WSGI (Passenger, uWSGI, etc.): +- `app/passenger_wsgi.py` - Already exists (Passenger) +- `app/wsgi.py` - Already exists (generic WSGI) + +Make sure your server is configured to use the appropriate WSGI file. + +### šŸ” Security Checklist Before Deployment + +- [ ] Change SECRET_KEY in config.py to a secure random value +- [ ] Verify users.json has ONLY hashed passwords (no plain text) +- [ ] Block web access to `/app/admin/` directory +- [ ] Block web access to `/app/data/` directory (except through API) +- [ ] Enable HTTPS/SSL on the server +- [ ] Set `SESSION_COOKIE_SECURE = True` in config.py if using HTTPS +- [ ] Set appropriate file permissions (755/644) +- [ ] Test login functionality after deployment +- [ ] Verify permissions system works (try accessing /users without permission) + +### šŸ“¤ Upload Methods + +#### **Option 1: FTP/SFTP** +Upload all the files listed above using your FTP client, maintaining the directory structure. + +#### **Option 2: Git** +If using Git: +```bash +git add app/app.py app/templates/* app/css/* app/data/users.json app/admin/* +git commit -m "Add login system, permissions, and user management" +git push +``` +Then on server: +```bash +git pull +pip install -r app/requirements.txt +# Restart web server +``` + +#### **Option 3: ZIP Archive** +Create a ZIP of the entire `app/` folder and extract on the server. + +### šŸ”„ Migration Steps for Existing Server + +If you already have a running server: + +1. **Backup Current Installation** + ```bash + cp -r app/ app_backup_$(date +%Y%m%d)/ + ``` + +2. **Upload New Files** + Upload all files listed in "Essential Updated Files" section + +3. **Update Dependencies** + ```bash + pip install -r app/requirements.txt + ``` + +4. **Update Existing Users** (if applicable) + If you have existing users without the permission structure, add permissions: + ```json + { + "username": "existing_user", + "password": "existing_hash", + "permissions": { + "manage_users": false, + "view_reports": true, + "create_quotes": true + } + } + ``` + +5. **Test in Maintenance Mode** + - Test login at `/login` + - Test user management at `/users` (as admin) + - Test main app still works + - Test permission checks + +6. **Restart Web Server** + ```bash + # Apache with Passenger + touch tmp/restart.txt + + # Or systemctl + sudo systemctl restart your-service-name + ``` + +### 🧪 Post-Deployment Testing + +Test these flows after deployment: + +1. **Login Flow** + - Navigate to `/login` + - Login with correct credentials + - Verify redirect to location selection (if multiple locations) + - Verify redirect to main app + +2. **Permission System** + - Login as admin user (Master) + - Access `/users` - should work + - Login as non-admin user + - Try to access `/users` - should see "Access Denied" + +3. **Password Change** + - Login as admin + - Go to User Management + - Click "Change Password" on a user + - Verify your password is required + - Change password and verify new password works + +4. **Location Selection** + - Login as user with multiple locations + - Verify location selection page shows + - Verify User Management button shows only for admins + - Select location and verify redirect to main app + +### ā— Troubleshooting + +**"500 Internal Server Error" after deployment:** +- Check server error logs +- Verify SECRET_KEY is set +- Ensure all dependencies installed +- Check file permissions + +**"Users not loading" or "No users shown":** +- Verify `data/users.json` uploaded correctly +- Check JSON format is valid +- Ensure web server can read the file + +**"Permission denied" when accessing files:** +- Check file ownership (should be web server user) +- Set correct permissions (755 for directories, 644 for files) + +**Session not persisting:** +- Verify SECRET_KEY is consistent +- Check cookie settings in config +- Ensure HTTPS if SESSION_COOKIE_SECURE is True + +### šŸ“ž Support + +After deployment, keep these files handy: +- Server error logs (usually in `/var/log/apache2/` or similar) +- `information/LOGIN_SYSTEM_README.md` - Login system documentation +- `information/PERMISSIONS_SYSTEM.md` - Permission system documentation + +### šŸŽ‰ Success Indicators + +You'll know deployment was successful when: +- āœ… Accessing `/` redirects to `/login` (if not logged in) +- āœ… Login with correct credentials works +- āœ… Master user can access `/users` +- āœ… Non-admin users see "Access Denied" at `/users` +- āœ… User info header shows in main app +- āœ… Logout works and redirects to login +- āœ… Password change requires admin verification diff --git a/information/DYNAMIC_IMAGE_API.md b/information/DYNAMIC_IMAGE_API.md new file mode 100644 index 0000000..5fdc48d --- /dev/null +++ b/information/DYNAMIC_IMAGE_API.md @@ -0,0 +1,476 @@ +# Dynamic Image Generation API + +## Overview +This system generates product images on-the-fly with configurable colors, hardware positions, and other options. It uses server-side image processing with PIL/Pillow to composite and recolor product images dynamically. + +## Setup + +### 1. Install Dependencies +```bash +pip install -r requirements.txt +``` + +This will install: +- Flask (web framework) +- Pillow (image processing) +- Other dependencies + +### 2. Prepare Product Images + +For the COBRAI demo product: +1. Save the white storm door image as `app/images/cobrai.jpg` +2. The configuration is already in `app/data/image_configs.json` + +### 3. Create Cache Directory +The cache directory will be created automatically when the app runs: +``` +app/cache/product_images/ +``` + +## API Endpoints + +### 1. Generate Product Image + +**Endpoint:** `GET /api/product-image/` + +**Parameters:** +- `color` - Color name (default: 'white') + - Options: white, black, bronze, sandstone +- `hinge` - Hinge side (default: 'right') + - Options: left, right +- `material` - Material type (default: 'aluminum') + - For future use +- `format` - Return format (default: 'image') + - Options: image, json +- `cache` - Use caching (default: 'true') + - Options: true, false + +**Examples:** + +Return image directly (for use in `` tags): +``` +GET http://localhost:8080/api/product-image/cobrai?color=black&hinge=right +GET http://localhost:8080/api/product-image/cobrai?color=bronze&hinge=left +``` + +Return JSON with base64 image: +``` +GET http://localhost:8080/api/product-image/cobrai?color=white&hinge=right&format=json +``` + +**Response (format=image):** +Raw PNG image file + +**Response (format=json):** +```json +{ + "status": "success", + "productCode": "cobrai", + "color": "black", + "hinge": "left", + "material": "aluminum", + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", + "format": "base64" +} +``` + +### 2. Get Product Configuration + +**Endpoint:** `GET /api/product-config/` + +Returns the complete configuration for a product including available colors, colorable regions, hardware positions, etc. + +**Example:** +``` +GET http://localhost:8080/api/product-config/cobrai +``` + +**Response:** +```json +{ + "status": "success", + "productCode": "cobrai", + "config": { + "sourceImage": "images/cobrai.jpg", + "productCode": "COBRAI", + "availableColors": { + "white": {"rgb": [255, 255, 255], "name": "White"}, + "black": {"rgb": [30, 30, 30], "name": "Black"}, + "bronze": {"rgb": [110, 80, 50], "name": "Bronze"} + }, + "colorableRegions": [...], + "hardwarePositions": {...}, + "metadata": {...} + } +} +``` + +### 3. Clear Image Cache + +**Endpoint:** `POST /api/clear-image-cache` + +Clears cached images. Useful when updating source images or configurations. + +**Request Body (Optional):** +```json +{ + "productCode": "cobrai" +} +``` + +**Response:** +```json +{ + "status": "success", + "message": "Cache cleared for cobrai" +} +``` + +## Configuration File Format + +Location: `app/data/image_configs.json` + +### Structure: + +```json +{ + "product_code": { + "sourceImage": "path/to/source.jpg", + "productCode": "PRODUCT_CODE", + "description": "Product description", + + "availableColors": { + "color_name": { + "rgb": [R, G, B], + "name": "Display Name" + } + }, + + "colorableRegions": [ + { + "name": "region_identifier", + "topLeft": [x, y], + "bottomRight": [x, y], + "description": "What this region is" + } + ], + + "hardwarePositions": { + "handle_right": { + "x": 580, + "y": 730, + "image": "path/to/hardware.png", + "flip": false + } + }, + + "glassRegions": [ + { + "name": "glass_area", + "topLeft": [x, y], + "bottomRight": [x, y], + "description": "Glass/screen area" + } + ], + + "metadata": { + "imageWidth": 720, + "imageHeight": 1450, + "defaultColor": "white", + "defaultHinge": "right" + }, + + "cache": { + "enabled": true, + "directory": "cache/product_images", + "maxAge": 86400 + } + } +} +``` + +### Key Concepts: + +#### Colorable Regions +Define rectangular areas to be recolored. The algorithm: +1. Preserves brightness/luminosity +2. Applies target color +3. Maintains shadows and highlights + +Coordinates are `[x, y]` where: +- `topLeft`: Upper-left corner +- `bottomRight`: Lower-right corner + +#### Hardware Positions +Define where handles, locks, etc. should be placed: +- `x, y`: Position to place hardware image +- `image`: Path to hardware PNG (with transparency) +- `flip`: Whether to flip horizontally (for left hinge) + +#### Glass Regions +Define areas that should remain unchanged (glass, screens). +Currently for documentation purposes; future feature. + +## Frontend Integration + +### Option 1: Direct Image URL +```html +Storm Door +``` + +### Option 2: JavaScript with Base64 +```javascript +async function loadProductImage(productCode, color, hinge) { + const response = await fetch( + `/api/product-image/${productCode}?color=${color}&hinge=${hinge}&format=json` + ); + const data = await response.json(); + + if (data.status === 'success') { + document.getElementById('product-img').src = data.image; + } +} + +// Usage +loadProductImage('cobrai', 'black', 'left'); +``` + +### Option 3: Dynamic URL Switching +```javascript +function updateProductImage(color, hinge) { + const img = document.getElementById('product-img'); + img.src = `/api/product-image/cobrai?color=${color}&hinge=${hinge}`; +} + +// On color change +document.getElementById('color-select').addEventListener('change', (e) => { + const color = e.target.value; + const hinge = document.querySelector('[name="hinge"]:checked').value; + updateProductImage(color, hinge); +}); +``` + +## How Image Processing Works + +### Color Application +1. **Load base image** - White or neutral colored product photo +2. **Define regions** - Specify rectangles for frame, panels, etc. +3. **Calculate brightness** - For each pixel, determine relative brightness +4. **Apply target color** - Colorize while preserving brightness variations +5. **Result** - Natural-looking colored product with preserved shadows/highlights + +### Brightness Preservation +```python +# For each pixel in region: +original_brightness = (r + g + b) / 3 +brightness_factor = original_brightness / 255.0 + +new_r = target_color_r * brightness_factor +new_g = target_color_g * brightness_factor +new_b = target_color_b * brightness_factor +``` + +This maintains shadows (darker pixels stay darker) and highlights (lighter pixels stay lighter). + +### Hardware Application +1. Load hardware PNG with transparency +2. Optionally flip horizontally for left hinge +3. Composite onto product image at specified position + +### Caching +- Generated images are cached using MD5 hash of parameters +- Cache key: `{product_code}_{color}_{hinge}_{material}` +- Stored as PNG in `cache/product_images/` +- Subsequent requests return cached version instantly + +## Testing the Endpoints + +### Using cURL + +Test basic image generation: +```bash +curl http://localhost:8080/api/product-image/cobrai?color=black > test_black.png +``` + +Test with different configurations: +```bash +curl http://localhost:8080/api/product-image/cobrai?color=bronze&hinge=left > test_bronze_left.png +``` + +Get JSON response: +```bash +curl http://localhost:8080/api/product-image/cobrai?color=white&format=json +``` + +Get configuration: +```bash +curl http://localhost:8080/api/product-config/cobrai +``` + +Clear cache: +```bash +curl -X POST http://localhost:8080/api/clear-image-cache -H "Content-Type: application/json" -d '{"productCode":"cobrai"}' +``` + +### Using Browser +Simply visit: +``` +http://localhost:8080/api/product-image/cobrai?color=black&hinge=left +``` + +### Using JavaScript Fetch +```javascript +// Get image as blob +fetch('/api/product-image/cobrai?color=bronze&hinge=left') + .then(response => response.blob()) + .then(blob => { + const url = URL.createObjectURL(blob); + document.getElementById('img').src = url; + }); + +// Get as JSON +fetch('/api/product-image/cobrai?color=black&format=json') + .then(response => response.json()) + .then(data => { + document.getElementById('img').src = data.image; + }); +``` + +## Adding New Products + +### Step 1: Prepare Source Image +- Photograph product in white or neutral color +- Clean background +- Good lighting +- High resolution (will be resized) +- Save as JPG in `app/images/` + +### Step 2: Determine Regions +Open image in image editor and note pixel coordinates: +- Frame edges (left, right, top, bottom) +- Panels/kickplates +- Rails/dividers + +Record as `topLeft [x, y]` and `bottomRight [x, y]` + +### Step 3: Add to Configuration +Add entry to `image_configs.json`: +```json +{ + "your_product": { + "sourceImage": "images/your_product.jpg", + "productCode": "YOUR-PRODUCT", + "availableColors": { + "white": {"rgb": [255, 255, 255], "name": "White"}, + "black": {"rgb": [30, 30, 30], "name": "Black"} + }, + "colorableRegions": [ + { + "name": "frame", + "topLeft": [0, 0], + "bottomRight": [100, 1450] + } + ], + "metadata": { + "imageWidth": 720, + "imageHeight": 1450, + "defaultColor": "white" + } + } +} +``` + +### Step 4: Test +```bash +curl http://localhost:8080/api/product-image/your_product?color=black > test.png +``` + +## Performance Notes + +### First Request +- ~100-500ms (depending on image size and region count) +- Includes image loading, processing, and caching + +### Cached Requests +- ~10-50ms +- Just file system read and serve + +### Memory Usage +- Base image kept in memory during processing +- Minimal memory footprint when served from cache + +### Optimization Tips +1. **Use caching** - Enabled by default +2. **Smaller source images** - 1000x1600px is usually sufficient +3. **Fewer regions** - Combine adjacent areas when possible +4. **CDN** - Serve cached images from CDN in production + +## Production Deployment + +### Recommended Setup +1. **Pre-generate** common combinations on deploy +2. **CDN** to serve cached images +3. **Redis cache** instead of filesystem (optional) +4. **Image optimization** - Use WebP format where supported +5. **Rate limiting** on generation endpoint + +### Pre-generation Script +```python +from image_generator import ProductImageGenerator + +generator = ProductImageGenerator() +products = ['cobrai', 'product2'] +colors = ['white', 'black', 'bronze'] +hinges = ['left', 'right'] + +for product in products: + for color in colors: + for hinge in hinges: + img = generator.generate_product_image(product, color, hinge) + print(f'Generated: {product} {color} {hinge}') +``` + +## Troubleshooting + +### "PIL/Pillow not installed" +```bash +pip install Pillow +``` + +### "Product not found in configuration" +Check that product code in URL matches key in `image_configs.json` + +### "Could not load base image" +Verify `sourceImage` path in config and that file exists + +### Colors look wrong +Adjust RGB values in `availableColors` section + +### Regions not coloring +1. Verify coordinates are within image bounds +2. Check that region isn't transparent +3. Ensure topLeft is actually top-left of bottomRight + +### Cache not working +Check that `cache/product_images/` directory is writable + +## Future Enhancements + +### Planned Features +- [ ] Material textures (wood grain, brushed metal) +- [ ] Glass tinting/color +- [ ] Shadow/lighting adjustments based on color +- [ ] Multiple hardware styles +- [ ] Size variations +- [ ] Decorative glass patterns +- [ ] WebP format support +- [ ] Batch generation CLI tool +- [ ] Admin UI for region configuration +- [ ] Automatic region detection (AI/ML) + +### Integration Ideas +- Direct integration with product configurator +- Real-time preview as user selects options +- Download high-res configured images +- Email configured image to customer +- Social media sharing with custom image diff --git a/information/FOLDER_STRUCTURE.md b/information/FOLDER_STRUCTURE.md new file mode 100644 index 0000000..3b5cc54 --- /dev/null +++ b/information/FOLDER_STRUCTURE.md @@ -0,0 +1,81 @@ +# Workspace Folder Structure + +This workspace is organized into clean, logical folders: + +## šŸ“ Root Directory + +``` +ā”œā”€ā”€ .env.example # Environment configuration template +ā”œā”€ā”€ .gitignore # Git ignore rules +ā”œā”€ā”€ .vscode/ # VS Code settings and tasks +ā”œā”€ā”€ app/ # šŸš€ Active project files (MAIN APPLICATION) +└── information/ # šŸ“š Documentation and deprecated files +``` + +## šŸš€ app/ - Active Project Files + +**All working application code and assets** + +- **app.py** - Main Flask application +- **config.py** - Application configuration +- **wsgi.py** / **passenger_wsgi.py** - Production WSGI servers +- **requirements.txt** - Python dependencies +- **parse_csv_to_json.py** - CSV to JSON data processor +- **generate_bitwise_helper.py** - Bitwise filtering data generator +- **css/** - Stylesheets +- **js/** - JavaScript files +- **templates/** - HTML templates +- **data/** - JSON data files +- **images/** - Image assets + +## šŸ“š information/ - Documentation + +**All project documentation and guides** + +- AI_PARSING_INSTRUCTIONS.md +- BITWISE_USAGE_GUIDE.md +- QUICKSTART.md +- README.md +- REQUIRED_CODE_CHANGES.md +- START_HERE.md +- URL_EXAMPLES.md +- URL_IMPLEMENTATION_SUMMARY.md +- URL_STATE_MANAGEMENT.md + +### information/deprecated/ - Old Files + +**Deprecated files kept for reference** + +- index.html / index2.html (replaced by templates/) +- codes.text (old data) +- run.bat (old batch file) +- Procfile (old deployment config) +- temp/ (temporary files) + +--- + +## Working with the Project + +### Running Tasks + +All VS Code tasks are configured to work with the new structure: + +- **Ctrl+Shift+B** - Process All Data (updates JSON from CSV) +- **Ctrl+Shift+P** → "Tasks: Run Task" → "Start Flask Server" + +### Updating Data + +When you update `app/data/products.csv`: +1. Press **Ctrl+Shift+B** to run "Process All Data" +2. This generates fresh JSON files in `app/data/` + +### Starting the Server + +1. Press **Ctrl+Shift+P** +2. Type "Tasks: Run Task" +3. Select "Start Flask Server" +4. Open http://127.0.0.1:8080 + +--- + +**Last Updated:** March 26, 2026 diff --git a/information/LAYERED_IMAGES.md b/information/LAYERED_IMAGES.md new file mode 100644 index 0000000..93b0a22 --- /dev/null +++ b/information/LAYERED_IMAGES.md @@ -0,0 +1,195 @@ +# Layered Image System Guide + +## Overview +The product finder supports a layered image system that allows dynamic product configuration (changing colors, materials, hinge location, etc.) without requiring a separate photo for every combination. + +## How It Works + +### System Architecture +Images are stacked in layers (like Photoshop layers): +1. **Base Layer** - House/frame (JPG) - the static background +2. **Door Layer** - The door panel (PNG with transparency) - changes with color selection +3. **Hardware Layer** - Handle/lock set (PNG with transparency) - can flip for left/right hinge +4. **Overlay Layer** - Glass view/decorative elements (PNG with transparency) - optional + +### Fallback Behavior +- **No layered config**: Shows the standard flat `image` field +- **Layered enabled but missing files**: Shows base layer + warning banner +- **User selects unavailable option**: Displays "Preview not available for this configuration" + +## JSON Configuration + +### Standard Product (Flat Image) +```json +{ + "productCode": "450", + "description": "#450 RAVEN STORM WINDOWS", + "image": "images/450.jpg", + "colors": ["White", "Black", "Bronze"], + "materials": ["Aluminum"] +} +``` + +### Product with Layered Images +```json +{ + "productCode": "404", + "description": "#404 FALCON STORM WINDOWS", + "image": "images/404.jpg", + "colors": ["White", "Black", "Bronze", "Sandstone"], + "materials": ["Aluminum"], + "imageConfig": { + "layered": true, + "basePath": "images/products/404/", + "layers": { + "base": "base.jpg", + "door": { + "white": "door-white.png", + "black": "door-black.png", + "bronze": "door-bronze.png", + "sandstone": "door-sandstone.png" + }, + "hardware": "handle.png", + "overlay": { + "inside": "view-inside.png", + "outside": "view-outside.png" + } + } + } +} +``` + +### Partial Layered Images (Some Colors Available) +```json +{ + "productCode": "505", + "description": "#505 DOOR", + "image": "images/505.jpg", + "colors": ["White", "Black", "Bronze", "Tan"], + "materials": ["Aluminum", "Vinyl"], + "imageConfig": { + "layered": true, + "basePath": "images/products/505/", + "layers": { + "base": "base.jpg", + "door": { + "white": "door-white.png", + "black": "door-black.png" + // Bronze and Tan not available yet - will show warning + }, + "hardware": "handle.png" + } + } +} +``` + +## File Structure + +### Recommended Directory Layout +``` +app/images/products/ +ā”œā”€ā”€ 404/ +│ ā”œā”€ā”€ base.jpg # Frame/house background +│ ā”œā”€ā”€ door-white.png # White door panel (transparent BG) +│ ā”œā”€ā”€ door-black.png # Black door panel (transparent BG) +│ ā”œā”€ā”€ door-bronze.png # Bronze door panel (transparent BG) +│ ā”œā”€ā”€ door-sandstone.png # Sandstone door panel (transparent BG) +│ ā”œā”€ā”€ handle.png # Hardware (transparent BG) +│ ā”œā”€ā”€ view-inside.png # Optional inside view overlay +│ └── view-outside.png # Optional outside view overlay +ā”œā”€ā”€ 450/ +│ ā”œā”€ā”€ base.jpg +│ └── door-white.png +└── [other products]/ +``` + +## Creating Layered Images + +### Requirements +- **Base image**: JPG format, includes frame, glass, background +- **Layer images**: PNG format with transparency +- **Consistent dimensions**: All layers for a product should be the same size +- **Alignment**: Layers must align perfectly when stacked + +### Photoshop/GIMP Workflow +1. Start with full product photo +2. Create separate layers for each component +3. Remove background from door/hardware layers +4. Export: + - Base layer → JPG + - Component layers → PNG (with transparency) +5. Create color variants by adjusting door layer + +### Photography Tips +- Use consistent lighting +- Photograph against neutral background (for easy removal) +- Keep camera/product position identical for all shots +- Consider photographing white version first, then recolor digitally + +## Features + +### Auto-Selection +- If product has only one color: auto-selected and dropdown disabled +- If no colors available: shows "N/A" and disabled + +### Hinge Location +- Right/Left hinge radio buttons flip the hardware layer horizontally +- Works with both flat and layered images + +### Dynamic Updates +- Color changes update the door layer instantly +- Missing images show warning instead of breaking + +### Sorting +- Materials: Alphabetically sorted +- Colors: Alphabetically sorted with White always at bottom + +## Testing Your Setup + +### 1. Test Flat Fallback +Set `"layered": false` or remove `imageConfig` entirely - should show standard image + +### 2. Test Missing Layer +Remove a color file - should show base + warning banner + +### 3. Test All Colors +Select each color - should swap door layer smoothly + +### 4. Test Hinge Flip +Toggle left/right hinge - hardware should flip horizontally + +## Troubleshooting + +### Images not showing +- Check file paths in `basePath` and layer filenames +- Verify files exist in `app/images/products/[code]/` +- Check browser console for 404 errors + +### Colors not matching +- Ensure color keys in JSON match available color names +- Keys should be lowercase in the door config (e.g., `"white"` not `"White"`) + +### Layers misaligned +- All images must be same dimensions +- Check that transparent PNGs aren't cropped differently + +### Warning banner always showing +- Verify the selected color exists in `layers.door` object +- Check that color value from dropdown matches JSON key + +## Migration Strategy + +### Phase 1: Keep Flat Images +Keep existing flat images as fallback while creating layered versions + +### Phase 2: Add Layered for Key Products +Focus on best-selling products first, add `imageConfig` gradually + +### Phase 3: Full Migration +Once all images ready, can remove flat images (but recommend keeping as fallback) + +## Performance Notes +- PNG layers are cached by browser +- Base image loads once, only door layer changes on color switch +- Much smaller file size than separate photos for each combination +- Example: Instead of 4 full photos (1MB each = 4MB), use 1 base (800KB) + 4 doors (200KB each = 800KB) = 1.6MB total diff --git a/information/LOGIN_SYSTEM_README.md b/information/LOGIN_SYSTEM_README.md new file mode 100644 index 0000000..d80d55e --- /dev/null +++ b/information/LOGIN_SYSTEM_README.md @@ -0,0 +1,312 @@ +# Login System Documentation + +## Overview +A complete authentication system for the CGW Product Finder that integrates with the user management system. Users must log in with their credentials to access the product finder application. + +## Features + +### šŸ” Secure Authentication +- Password verification using PBKDF2-SHA256 hashing +- Session-based authentication with HTTP-only cookies +- Automatic session management +- Active/inactive user account checking + +### šŸ“ Multi-Location Support +- Automatic location selection for single-location users +- Location selection page for users with multiple accessible locations +- Default location preference +- Location-based access control + +### šŸ›”ļø Security Features +- Login required decorator protects all main routes +- Inactive accounts are automatically blocked +- Sessions expire on logout or server restart +- Secure session cookies (HTTP-only, SameSite) + +## User Flow + +### 1. Login Process +1. User visits the app → Redirected to `/login` +2. User enters **username** and **password** +3. System validates credentials against `users.json` +4. System checks if user account is **active** +5. If valid and active: + - Session is created + - User accessible locations are loaded + +### 2. Location Selection (if applicable) +- **Single Accessible Location**: User goes directly to main app (location selection bypassed) +- **Multiple Accessible Locations**: User is redirected to `/select-location` + - Shows all accessible locations + - Default location is pre-selected + - User can choose their working location + - Selection is saved to session +- **Note**: Default location is automatically marked as accessible when user is created + +### 3. Main Application Access +- User accesses the Product Finder +- User info displayed in header (username + current location) +- Logout button available in header + +## Routes + +### Public Routes (No Login Required) +- `GET /login` - Login page +- `POST /api/login` - Login endpoint +- `GET /users` - User management page + +### Protected Routes (Login Required) +- `GET /` - Main product finder app +- `GET /quiz` - Quiz page (alias for main app) +- `GET /image-test` - Image generation test page +- `GET /select-location` - Location selection page +- `POST /api/select-location` - Set current location + +### Session Routes +- `GET /api/session` - Get current session info +- `POST /api/logout` - Logout and clear session + +## API Endpoints + +### POST /api/login +Authenticate user and create session. + +**Request:** +```json +{ + "username": "john_doe", + "password": "password123" +} +``` + +**Success Response:** +```json +{ + "status": "success", + "message": "Login successful", + "requiresLocationSelection": true +} +``` + +**Error Responses:** +```json +{ + "status": "error", + "message": "Invalid username or password" +} +``` + +```json +{ + "status": "error", + "message": "Account is inactive. Please contact an administrator." +} +``` + +### GET /api/session +Get current user session information. + +**Response:** +```json +{ + "status": "success", + "username": "john_doe", + "defaultLocation": "LINDS", + "currentLocation": "KC", + "accessibleLocations": ["LINDS", "KC", "IOLA"] +} +``` + +### POST /api/select-location +Select a location for the current session. + +**Request:** +```json +{ + "location": "KC" +} +``` + +**Response:** +```json +{ + "status": "success", + "message": "Location selected", + "currentLocation": "KC" +} +``` + +### POST /api/logout +Log out and clear session. + +**Response:** +```json +{ + "status": "success", + "message": "Logged out successfully" +} +``` + +## Session Data + +The session stores: +- `user_id`: Index of user in users.json +- `username`: Username string +- `defaultLocation`: User's default location code +- `currentLocation`: Currently selected location code +- `accessibleLocations`: Array of location codes user can access + +## Authentication Decorator + +The `@login_required` decorator protects routes: + +```python +@app.route('/protected-page') +@login_required +def protected_page(): + return render_template('protected.html') +``` + +The decorator: +1. Checks if user is logged in (has `user_id` in session) +2. Validates user still exists in users.json +3. Checks if user account is still active +4. Redirects to login if any check fails + +## Getting Current User in Routes + +```python +@app.route('/my-route') +@login_required +def my_route(): + user = get_current_user() + username = session.get('username') + current_location = session.get('currentLocation') + + # Use user data... + return render_template('page.html') +``` + +## Location-Based Access Control + +Users can only access locations they have permission for: + +```python +# In users.json +{ + "username": "john_doe", + "locationSettings": { + "LINDS": { "accessible": true }, + "IOLA": { "accessible": false }, + "KC": { "accessible": true }, + "BMD": { "accessible": false } + } +} +``` + +This user can access: +- āœ… Lindsborg (LINDS) +- āœ… KC (KC) +- āŒ Iola (IOLA) +- āŒ BMD (BMD) + +## User Interface Components + +### Login Page (`/login`) +- Clean, centered login form +- Username and password fields +- Submit button with loading spinner +- Link to User Management page +- Error message display + +### Location Selection Page (`/select-location`) +- Shows logged-in username +- Shows default location +- Radio buttons for each accessible location +- Default location is pre-selected +- Continue and Logout buttons + +### Main App Header +- User info display: `šŸ‘¤ username | šŸ“ location` +- Logout button in header +- Positioned in top-right corner + +## Security Considerations + +### Password Security +- Passwords are hashed using PBKDF2-SHA256 +- Hashes are never reversed or displayed +- Hash verification happens server-side only + +### Session Security +- Sessions use secure random keys +- Cookies are HTTP-only (not accessible via JavaScript) +- SameSite cookie policy prevents CSRF attacks +- Sessions cleared on logout + +### Account Status +- Inactive accounts cannot log in +- If account is deactivated while logged in, next request will log them out +- User must have at least one accessible location + +## Testing the Login System + +### Test User Creation +1. Go to `/users` +2. Create a test user: + - Username: `testuser` + - Password: `password123` + - Default Location: Lindsborg + - Check "Accessible" for Lindsborg and KC + +### Test Login Flow +1. Go to `/` (should redirect to `/login`) +2. Enter credentials: `testuser` / `password123` +3. Click "Sign In" +4. Since user has 2 accessible locations → redirected to `/select-location` +5. Choose a location and click "Continue" +6. Now viewing main Product Finder app +7. See user info in header +8. Click "Logout" to end session + +### Test Single Location User +1. Create user with only 1 accessible location +2. Log in +3. Should go directly to main app (skip location selection) + +### Test Inactive User +1. Create and log in as a user +2. In User Management, toggle user to "Inactive" +3. Try to log in → Should see "Account is inactive" message + +## Troubleshooting + +### "Please log in first" on all pages +- Session may have expired +- Server may have restarted (sessions are in-memory) +- Clear browser cookies and log in again + +### "User not found" error +- User may have been deleted while logged in +- Log out and log back in + +### Can't access certain locations +- Check user's "Accessible" checkboxes in User Management +- User must have at least one accessible location + +### Stuck on location selection page +- User must have multiple accessible locations +- If this shouldn't happen, check user's location settings +- Or click "Sign Out" and contact administrator + +## Future Enhancements + +Potential additions: +- [ ] Remember me checkbox (persistent sessions) +- [ ] Password reset functionality +- [ ] Session timeout after inactivity +- [ ] Login attempt limiting (brute force protection) +- [ ] Two-factor authentication +- [ ] Session management dashboard +- [ ] Location switching without re-login +- [ ] Audit log of login attempts diff --git a/information/PERMISSIONS_QUICKSTART.md b/information/PERMISSIONS_QUICKSTART.md new file mode 100644 index 0000000..cb2e980 --- /dev/null +++ b/information/PERMISSIONS_QUICKSTART.md @@ -0,0 +1,264 @@ +# WordPress-Style Permission System - Quick Reference + +## āœ… What Was Implemented + +The CGW Product Finder now has a complete WordPress-style permission system with: + +1. **Permission Checking Functions** (Backend - Python) + - `can_user(permission, location=None)` - Check single permission + - `user_has_any_permission(permissions, location=None)` - Check if user has ANY permission + - `user_has_all_permissions(permissions, location=None)` - Check if user has ALL permissions + - `get_user_permissions(location=None)` - Get all user permissions + - `@permission_required(permission, location=None)` - Route decorator for permission protection + +2. **Permission Check Endpoints** (Frontend - API) + - `POST /api/check-permission` - Check if user has specific permission + - `GET /api/user-permissions` - Get all user permissions + +3. **Access Denial** + - Beautiful access denied page at `templates/access_denied.html` + - Shows required permission and helpful navigation + +4. **Protected Routes** + - `/users` - User management page (requires `manage_users`) + - `/api/users` (GET, POST, PATCH, DELETE) - All user management endpoints protected + +5. **User Data Structure** + - Global permissions: `user.permissions` + - Location-specific permissions: `user.locationSettings[LOCATION].permissions` + +## šŸš€ Quick Start Usage + +### Backend (Python) + +```python +# Check permission +if can_user('create_quotes'): + # User can create quotes + pass + +# Protect a route +@app.route('/admin/reports') +@login_required +@permission_required('view_reports') +def admin_reports(): + return render_template('reports.html') + +# Check at specific location +if can_user('manage_inventory', location='LINDS'): + # User can manage inventory at Lindsborg + pass +``` + +### Frontend (JavaScript) + +```javascript +// Check single permission +const response = await fetch('/api/check-permission', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ permission: 'create_quotes' }) +}); +const data = await response.json(); +if (data.hasPermission) { + // Show create button +} + +// Get all permissions +const response = await fetch('/api/user-permissions'); +const data = await response.json(); +console.log(data.permissions); // { manage_users: true, create_quotes: true, ... } +``` + +## šŸ“ Files Modified/Created + +### Created: +- `app/templates/access_denied.html` - Access denial page +- `app/PERMISSIONS_SYSTEM.md` - Comprehensive documentation + +### Modified: +- `app/app.py` - Added permission checking functions and protected routes +- `app/data/users.json` - Added permissions to existing users +- `app/data/example_user_structure.json` - Updated with permission examples + +## šŸ‘„ Current Users & Permissions + +### Master (Admin) +- **Password**: Master +- **Location**: KC (access to all locations) +- **Permissions**: Full access + - manage_users āœ… + - view_reports āœ… + - create_quotes āœ… + - approve_quotes āœ… + - manage_products āœ… + - manage_inventory āœ… + +### Darlene (Standard User) +- **Password**: Darlene +- **Location**: IOLA (access to LINDS, IOLA, KC) +- **Permissions**: Limited access + - manage_users āœ… + - view_reports āœ… + - create_quotes āœ… + - approve_quotes āŒ + - manage_products āŒ + - manage_inventory āŒ + +## šŸ”’ Permission Hierarchy + +``` +Check Order: +1. Is user logged in? → If no: return False +2. Is user active? → If no: return False +3. Check global permissions (user.permissions) → If found: return value +4. Check location-specific permissions → If found: return value +5. Default: return False +``` + +## šŸŽÆ Common Permission Names + +Recommended permissions for your system: + +**User Management:** +- `manage_users` - Create, edit, delete users (already implemented) +- `view_users` - View user list +- `reset_passwords` - Reset passwords + +**Products & Inventory:** +- `manage_products` - Add/edit/delete products +- `view_products` - View product catalog +- `manage_inventory` - Adjust inventory +- `view_inventory` - View inventory + +**Quotes & Orders:** +- `create_quotes` - Create quotes +- `view_quotes` - View quotes +- `approve_quotes` - Approve/reject quotes +- `edit_quotes` - Edit quotes + +**Reports:** +- `view_reports` - Access reports +- `export_data` - Export data +- `view_analytics` - View analytics + +## 🧪 Testing the System + +### Test 1: User Management Access +```bash +1. Start Flask server: python app/app.py +2. Login as Master (password: Master) +3. Navigate to /users +4. Should see user management interface āœ… +``` + +### Test 2: Permission Denied +```bash +1. Create a new user without manage_users permission +2. Login as that user +3. Navigate to /users +4. Should see "Access Denied" page āœ… +``` + +### Test 3: API Permission Check +```bash +# In browser console after login: +const response = await fetch('/api/check-permission', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({permission: 'manage_users'}) +}); +const data = await response.json(); +console.log(data.hasPermission); // Should be true for Master +``` + +### Test 4: Get All Permissions +```bash +# In browser console after login: +const response = await fetch('/api/user-permissions'); +const data = await response.json(); +console.log(data.permissions); // Should show all user's permissions +``` + +## šŸ“ Next Steps + +To add permissions to new features: + +1. **Define Permission Name** + ```python + # Choose a descriptive name like 'create_quotes' + ``` + +2. **Protect Backend Route** + ```python + @app.route('/quotes/new') + @login_required + @permission_required('create_quotes') + def new_quote(): + return render_template('new_quote.html') + ``` + +3. **Check in Code** + ```python + if can_user('create_quotes'): + # Allow quote creation + ``` + +4. **Hide/Show Frontend Elements** + ```javascript + const perms = await fetch('/api/user-permissions').then(r => r.json()); + if (perms.permissions.create_quotes) { + document.getElementById('createBtn').style.display = 'block'; + } + ``` + +5. **Add to User Data** + ```json + { + "username": "user", + "permissions": { + "create_quotes": true + } + } + ``` + +## šŸ› ļø Troubleshooting + +**Access Denied even with permission:** +- Check spelling of permission name (case-sensitive) +- Verify user is active in users.json +- Clear browser cookies and re-login +- Check server logs for errors + +**Permission check returns False:** +- Ensure user is logged in +- Verify permission exists in users.json +- Check if using correct location context +- Confirm session is valid + +**Frontend shows button but backend denies:** +- This is correct! Frontend checks are for UX only +- Backend always enforces permissions +- Never trust client-side permission checks + +## šŸ“š Full Documentation + +See `app/PERMISSIONS_SYSTEM.md` for complete documentation including: +- Detailed examples +- Best practices +- Security notes +- Migration guide +- Advanced usage patterns + +## šŸŽ‰ Summary + +You now have a fully functional WordPress-style permission system that allows: +- āœ… Fine-grained access control +- āœ… Global and location-specific permissions +- āœ… Easy permission checks in code +- āœ… Protected routes with decorators +- āœ… Frontend permission checking +- āœ… Beautiful access denied pages +- āœ… Flexible permission inheritance + +The system is secure, scalable, and follows WordPress best practices! diff --git a/information/PERMISSIONS_SYSTEM.md b/information/PERMISSIONS_SYSTEM.md new file mode 100644 index 0000000..c06d548 --- /dev/null +++ b/information/PERMISSIONS_SYSTEM.md @@ -0,0 +1,376 @@ +# Permission System Documentation + +The CGW Product Finder uses a WordPress-style permission system that allows fine-grained control over what users can do both globally and at specific locations. + +## Overview + +Permissions can be set at two levels: +1. **Global Permissions**: Apply across all locations (stored in user's `permissions` field) +2. **Location-Specific Permissions**: Apply only at specific locations (stored in `locationSettings[LOCATION].permissions`) + +Permission checks follow this hierarchy: +- First checks global permissions +- Then checks location-specific permissions +- Location-specific permissions can override global permissions +- If a permission isn't found anywhere, it defaults to `false` + +## User Structure + +```json +{ + "username": "john_doe", + "password": "pbkdf2:sha256:1000000$...", + "defaultLocation": "LINDS", + "active": true, + "permissions": { + "manage_users": true, + "view_reports": true, + "create_quotes": true + }, + "locationSettings": { + "LINDS": { + "accessible": true, + "permissions": { + "manage_inventory": true, + "approve_quotes": true + } + }, + "IOLA": { + "accessible": true, + "permissions": { + "manage_inventory": false, + "approve_quotes": false + } + } + } +} +``` + +## Backend Usage (Python) + +### Checking Permissions in Code + +```python +from app import can_user + +# Check if user has permission at current location +if can_user('create_quotes'): + # User can create quotes + pass + +# Check if user has permission at specific location +if can_user('manage_inventory', location='LINDS'): + # User can manage inventory at Lindsborg + pass + +# Check only global permissions (ignore location-specific) +if can_user('manage_users', location='global'): + # User has global user management permission + pass +``` + +### Protecting Routes with Decorators + +```python +from app import permission_required, login_required + +@app.route('/admin/users') +@login_required +@permission_required('manage_users') +def admin_users(): + """Only users with manage_users permission can access""" + return render_template('admin_users.html') + +# Check permission at specific location +@app.route('/inventory/') +@login_required +@permission_required('manage_inventory', location_param='location') +def location_inventory(location): + """Permission checked for the location in URL parameter""" + return render_template('inventory.html') +``` + +### Multiple Permission Checks + +```python +from app import user_has_any_permission, user_has_all_permissions + +# Check if user has ANY of these permissions +if user_has_any_permission(['create_quotes', 'approve_quotes']): + # User can either create OR approve quotes + pass + +# Check if user has ALL of these permissions +if user_has_all_permissions(['manage_users', 'view_reports']): + # User has both permissions + pass +``` + +### Getting All User Permissions + +```python +from app import get_user_permissions + +# Get all permissions (global + current location) +permissions = get_user_permissions() +# Returns: {'manage_users': True, 'create_quotes': True, ...} + +# Get permissions for specific location +permissions = get_user_permissions(location='LINDS') + +# Get only global permissions +permissions = get_user_permissions(location='global') +``` + +## Frontend Usage (JavaScript) + +### Checking Single Permission + +```javascript +async function checkPermission(permissionName, location = null) { + try { + const response = await fetch('/api/check-permission', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + permission: permissionName, + location: location // Optional + }) + }); + + const data = await response.json(); + return data.hasPermission; + } catch (error) { + console.error('Error checking permission:', error); + return false; + } +} + +// Usage +if (await checkPermission('create_quotes')) { + // Show create quote button + document.getElementById('createQuoteBtn').style.display = 'block'; +} +``` + +### Getting All User Permissions + +```javascript +async function getUserPermissions(location = null) { + try { + const url = location + ? `/api/user-permissions?location=${location}` + : '/api/user-permissions'; + + const response = await fetch(url); + const data = await response.json(); + return data.permissions; + } catch (error) { + console.error('Error fetching permissions:', error); + return {}; + } +} + +// Usage +const permissions = await getUserPermissions(); +if (permissions.manage_users) { + // Show admin menu +} +``` + +### Show/Hide Elements Based on Permissions + +```javascript +async function initializePermissions() { + const permissions = await getUserPermissions(); + + // Show/hide elements + document.querySelectorAll('[data-permission]').forEach(element => { + const requiredPermission = element.dataset.permission; + if (!permissions[requiredPermission]) { + element.style.display = 'none'; + } + }); +} + +// In HTML: +// +//
Admin Panel
+``` + +## Common Permissions + +Here are some suggested permission names for the Product Finder: + +### User Management +- `manage_users` - Create, edit, delete users +- `view_users` - View user list +- `reset_passwords` - Reset other users' passwords + +### Product & Inventory +- `manage_products` - Add/edit/delete products +- `view_products` - View product catalog +- `manage_inventory` - Adjust inventory levels +- `view_inventory` - View inventory levels + +### Quotes & Orders +- `create_quotes` - Create new quotes +- `view_quotes` - View quotes +- `approve_quotes` - Approve/reject quotes +- `edit_quotes` - Edit existing quotes +- `delete_quotes` - Delete quotes + +### Reports & Data +- `view_reports` - Access reporting tools +- `export_data` - Export data to CSV/Excel +- `view_analytics` - View analytics dashboard + +### System Settings +- `manage_settings` - Change system settings +- `manage_locations` - Add/edit location settings +- `view_logs` - View system logs + +## Permission Flow Examples + +### Example 1: Creating a Quote + +```python +@app.route('/api/quotes', methods=['POST']) +@login_required +@permission_required('create_quotes') +def create_quote(): + # User needs create_quotes permission at their current location + data = request.json + # Create quote logic... + return jsonify({'status': 'success'}) +``` + +### Example 2: Approving Quotes (Location-Specific) + +```python +@app.route('/api/quotes//approve', methods=['POST']) +@login_required +def approve_quote(quote_id): + # Check permission at the quote's location + quote = get_quote(quote_id) + + if not can_user('approve_quotes', location=quote['location']): + return render_template('access_denied.html', + required_permission='approve_quotes'), 403 + + # Approve quote logic... + return jsonify({'status': 'success'}) +``` + +### Example 3: Multi-Location Access + +```python +@app.route('/api/inventory/transfer', methods=['POST']) +@login_required +def transfer_inventory(): + data = request.json + from_location = data['from_location'] + to_location = data['to_location'] + + # User must have manage_inventory at BOTH locations + if not user_has_all_permissions(['manage_inventory'], location=from_location): + return jsonify({'error': 'No permission at source location'}), 403 + + if not user_has_all_permissions(['manage_inventory'], location=to_location): + return jsonify({'error': 'No permission at destination location'}), 403 + + # Transfer logic... + return jsonify({'status': 'success'}) +``` + +## Access Denied Page + +When a user lacks permission, they see an access denied page that shows: +- Clear "Access Denied" message +- The specific permission that was required +- Options to go back or return home +- Contact information for requesting access + +## Best Practices + +1. **Be Specific**: Use descriptive permission names like `create_quotes` instead of `quotes` +2. **Granular Control**: Separate permissions (create, view, edit, delete) rather than one "manage" permission +3. **Check Early**: Check permissions at route level with decorators when possible +4. **Check Often**: Re-check permissions before critical operations, not just at page load +5. **Fail Secure**: Default to denying access if permission isn't explicitly granted +6. **Location Context**: Always consider whether a permission should be global or location-specific +7. **UI Feedback**: Hide/disable UI elements users can't use based on permissions +8. **Clear Errors**: Show helpful error messages when permission is denied + +## Adding New Permissions + +To add a new permission: + +1. **Define the permission** in your data structure (add to user's `permissions` or `locationSettings[LOCATION].permissions`) +2. **Protect routes** with `@permission_required('new_permission')` +3. **Check in code** with `can_user('new_permission')` +4. **Update frontend** to show/hide elements based on permission +5. **Document** the permission in this file + +## Troubleshooting + +### Permission check returns False but user should have access +- Check if permission is spelled correctly (case-sensitive) +- Verify user is logged in (`session['user_id']` exists) +- Check user's `active` status +- Verify permission exists in either global or location-specific permissions +- Check if using correct location (current vs specific vs global) + +### Access denied page shows even for users with permission +- Ensure decorators are in correct order: `@login_required` before `@permission_required` +- Check that permission name matches exactly +- Verify user data was saved correctly in users.json +- Clear browser cache/cookies if session is stale + +### Frontend shows elements but backend denies access +- Frontend permission checks are for UX only - always enforce in backend +- Make sure frontend is checking the same permission name +- Ensure frontend is checking at the same location context + +## Security Notes + +- **Never trust frontend permission checks** - they're for UI only +- **Always validate permissions on the backend** before performing operations +- **Session security** - permissions are loaded from session, which is server-side +- **Password hashing** - uses PBKDF2-SHA256 with 1M iterations +- **HTTP-only cookies** - session cookies cannot be accessed by JavaScript +- **Permission inheritance** - location-specific permissions override global ones + +## Migration Guide + +If you have existing users without permissions, you can add default permissions: + +```python +import json + +def add_default_permissions(): + with open('data/users.json', 'r') as f: + users = json.load(f) + + for user in users: + # Add global permissions if missing + if 'permissions' not in user: + user['permissions'] = { + 'view_products': True, + 'create_quotes': True, + 'manage_users': False # Admin only + } + + # Add location permissions if missing + for location in user.get('locationSettings', {}): + if 'permissions' not in user['locationSettings'][location]: + user['locationSettings'][location]['permissions'] = { + 'manage_inventory': False, + 'approve_quotes': False + } + + with open('data/users.json', 'w') as f: + json.dump(users, f, indent=2) +``` diff --git a/information/PRODUCT_FINDER_DEPLOYMENT.md b/information/PRODUCT_FINDER_DEPLOYMENT.md new file mode 100644 index 0000000..bbaf879 --- /dev/null +++ b/information/PRODUCT_FINDER_DEPLOYMENT.md @@ -0,0 +1,334 @@ +# /product-finder Deployment Fix Guide + +## šŸ”“ Problem + +The application works locally at `http://localhost:8080/` but fails on the server at `https://columbiawindows.com/product-finder/`. + +The redirect from `/product-finder` → `/product-finder/login` works, but then the login page or subsequent routes fail. + +## āœ… Solution Applied + +Three critical changes were made to fix subdirectory deployment: + +### 1. **Added PrefixMiddleware to app.py** + +This middleware tells Flask about the `/product-finder` prefix by setting `SCRIPT_NAME` in the WSGI environment: + +```python +class PrefixMiddleware: + """Middleware to handle subdirectory deployments""" + def __init__(self, app, prefix=''): + self.app = app + self.prefix = prefix.rstrip('/') + + def __call__(self, environ, start_response): + if self.prefix and self.prefix != '/': + path = environ.get('PATH_INFO', '') + script_name = environ.get('SCRIPT_NAME', '') + + if not script_name.startswith(self.prefix): + environ['SCRIPT_NAME'] = self.prefix + script_name + if path.startswith(self.prefix): + environ['PATH_INFO'] = path[len(self.prefix):] + + return self.app(environ, start_response) +``` + +This is automatically applied when `APPLICATION_ROOT` is set. + +### 2. **Updated passenger_wsgi.py** + +Sets the `APPLICATION_ROOT` environment variable before importing the app: + +```python +if 'APPLICATION_ROOT' not in os.environ: + os.environ['APPLICATION_ROOT'] = '/product-finder' +``` + +### 3. **Updated config.py** + +Production configuration now defaults to `/product-finder`: + +```python +class ProductionConfig(Config): + APPLICATION_ROOT = os.environ.get('APPLICATION_ROOT', '/product-finder') +``` + +Development still uses `/` for local testing. + +## šŸ“¤ Files to Upload + +Upload these updated files to your server: + +1. **`app/app.py`** - Contains PrefixMiddleware +2. **`app/passenger_wsgi.py`** - Sets APPLICATION_ROOT env var +3. **`app/config.py`** - Production defaults to /product-finder +4. **`app/.htaccess`** - Apache configuration (new file) +5. **All template files** - Already updated with url_for() and BASE_URL + +## šŸ”§ Server Configuration + +### Option A: Using .htaccess (Recommended) + +The `.htaccess` file is already configured for `/product-finder`. Upload it to your `app/` folder on the server. + +**Important:** Update these lines in `.htaccess`: +```apache +PassengerAppRoot /home/USERNAME/public_html/product-finder +PassengerPython /usr/bin/python3 # Update to your Python path +``` + +### Option B: Apache Virtual Host Configuration + +If you have access to Apache config, add this to your virtual host: + +```apache + + SetEnv APPLICATION_ROOT /product-finder + PassengerEnabled on + PassengerAppRoot /home/USERNAME/public_html/product-finder + PassengerPython /usr/bin/python3 + Allow from all + Options -MultiViews + +``` + +## šŸš€ Deployment Steps + +1. **Backup Current Installation** + ```bash + mv product-finder product-finder.backup + ``` + +2. **Upload Updated Files** + - Upload entire `app/` folder to server + - Or just upload the 5 changed files listed above + +3. **Restart Passenger** + ```bash + # In product-finder folder + mkdir -p tmp + touch tmp/restart.txt + ``` + +4. **Test the Application** + - Go to: `https://columbiawindows.com/product-finder/` + - Should redirect to: `https://columbiawindows.com/product-finder/login` + - Login page should load with all CSS/JS + - Login should work and redirect properly + - All routes should work: `/product-finder/users`, etc. + +## šŸ› Troubleshooting + +### Issue: Still getting 404 on login page + +**Check server error logs:** +```bash +tail -f ~/logs/error_log # or wherever your error logs are +``` + +**Verify APPLICATION_ROOT is set:** +Add this test route to app.py temporarily: +```python +@app.route('/debug-config') +def debug_config(): + return jsonify({ + 'APPLICATION_ROOT': app.config.get('APPLICATION_ROOT'), + 'script_root': request.script_root, + 'url_root': request.url_root, + 'base_url': request.base_url + }) +``` + +Then visit: `https://columbiawindows.com/product-finder/debug-config` + +**Expected response:** +```json +{ + "APPLICATION_ROOT": "/product-finder", + "script_root": "/product-finder", + "url_root": "https://columbiawindows.com/product-finder/", + "base_url": "https://columbiawindows.com/product-finder/debug-config" +} +``` + +### Issue: CSS/JS files not loading + +**Check that routes are working:** +- Visit: `https://columbiawindows.com/product-finder/css/styles.css` +- Should return CSS file, not 404 + +**Check .htaccess MIME types:** +Ensure these lines are in `.htaccess`: +```apache +AddType text/css .css +AddType application/javascript .js +AddType application/json .json +``` + +### Issue: Login works but redirects are wrong + +**Check redirect code in templates:** +All JavaScript should use `BASE_URL`: +```javascript +const BASE_URL = '{{ base_url }}'; +window.location.href = BASE_URL + '/login'; +``` + +All Python redirects should use `url_for()`: +```python +return redirect(url_for('login_page')) +``` + +### Issue: Works locally, fails on server + +**Verify environment:** +```bash +# SSH to server +cd ~/public_html/product-finder +python3 -c "import os; print(os.environ.get('APPLICATION_ROOT', 'NOT SET'))" +``` + +Should print: `/product-finder` + +**Check Passenger is using correct Python:** +```bash +which python3 +# Use this path in PassengerPython directive +``` + +### Issue: Sessions not persisting + +**Check SECRET_KEY:** +```python +# In config.py, production should have a fixed SECRET_KEY +SECRET_KEY = 'your-fixed-secret-key-here' # Don't use secrets.token_hex() in production +``` + +**Check cookie settings:** +Session cookies need to work with the subdirectory path. + +### Issue: API calls return 404 + +**Check browser console:** +Press F12, go to Network tab, and check the actual URLs being called. + +**Should see:** +``` +https://columbiawindows.com/product-finder/api/login +https://columbiawindows.com/product-finder/api/session +``` + +**If you see:** +``` +https://columbiawindows.com/api/login āŒ Missing prefix +``` + +Then `BASE_URL` is not set correctly in template. + +## āœ… Verification Checklist + +After deployment, test these in order: + +- [ ] Visit `https://columbiawindows.com/product-finder/` + - Should redirect to `/product-finder/login` āœ“ + +- [ ] Login page loads + - CSS styled correctly āœ“ + - No 404s in browser console āœ“ + +- [ ] Login with Master/Master + - Should redirect to `/product-finder/select-location` āœ“ + +- [ ] Select a location + - Should redirect to `/product-finder/` āœ“ + +- [ ] User info shows in header āœ“ + +- [ ] Click "User Management" (if Master user) + - Should go to `/product-finder/users` āœ“ + +- [ ] Logout + - Should return to `/product-finder/login` āœ“ + +## šŸ“ Key Points + +1. **The middleware is critical** - It tells Flask about the `/product-finder` prefix +2. **passenger_wsgi.py sets the env var** - Before importing the app +3. **All templates use BASE_URL** - For JavaScript fetch calls +4. **All routes use url_for()** - For Python redirects +5. **Production config defaults to /product-finder** - Development stays at / + +## šŸ”„ Rolling Back + +If something goes wrong: + +```bash +# Remove new files +rm -rf product-finder + +# Restore backup +mv product-finder.backup product-finder + +# Restart Passenger +touch product-finder/tmp/restart.txt +``` + +## šŸ“ž Still Having Issues? + +Run this diagnostic script on the server: + +```python +# Save as test_deployment.py in product-finder folder +import os +import sys + +print("=" * 60) +print("DEPLOYMENT DIAGNOSTIC") +print("=" * 60) +print(f"Python Version: {sys.version}") +print(f"Current Directory: {os.getcwd()}") +print(f"APPLICATION_ROOT env: {os.environ.get('APPLICATION_ROOT', 'NOT SET')}") +print() + +try: + os.environ['APPLICATION_ROOT'] = '/product-finder' + from app import app + print("āœ“ App imported successfully") + print(f"APPLICATION_ROOT config: {app.config.get('APPLICATION_ROOT')}") + print(f"Middleware applied: {'PrefixMiddleware' in str(type(app.wsgi_app))}") +except Exception as e: + print(f"āœ— Error importing app: {e}") + import traceback + traceback.print_exc() +``` + +Run with: `python3 test_deployment.py` + +## šŸŽÆ Expected Behavior + +**Before these changes:** +- Redirect works: `/product-finder` → `/product-finder/login` āœ“ +- Login page loads BUT Flask doesn't know about `/product-finder` prefix +- All `url_for()` calls generate `/login` instead of `/product-finder/login` āŒ +- Result: 404 errors on subpages + +**After these changes:** +- Flask knows it's at `/product-finder` via middleware āœ“ +- All `url_for()` generates `/product-finder/login` āœ“ +- All templates use `BASE_URL = '/product-finder'` āœ“ +- Result: Everything works āœ“ + +## šŸ†˜ Quick Fix Checklist + +If deployed and not working: + +1. [ ] Uploaded `app/app.py` with PrefixMiddleware? +2. [ ] Uploaded `app/passenger_wsgi.py` with env var setting? +3. [ ] Uploaded `app/.htaccess` with SetEnv directive? +4. [ ] Ran `touch tmp/restart.txt` to restart Passenger? +5. [ ] Checked error logs for Python errors? +6. [ ] Tested `/product-finder/debug-config` route? +7. [ ] Verified cookies are being set (F12 > Application > Cookies)? + +If all checked and still failing, check server error logs for the actual Python error. diff --git a/information/QUICKSTART.md b/information/QUICKSTART.md new file mode 100644 index 0000000..6ea087b --- /dev/null +++ b/information/QUICKSTART.md @@ -0,0 +1,47 @@ +# Product Finder Quiz - Quick Start Guide + +## Installation and Running + +### Step 1: Install Python +Make sure Python 3.8+ is installed: +```bash +python --version +``` + +### Step 2: Install Dependencies +```bash +pip install -r requirements.txt +``` + +### Step 3: Run the Application +```bash +python app.py +``` + +### Step 4: Access the Application +Open your browser and go to: +- **Quiz Page**: http://localhost:8080/quiz +- **Main Page**: http://localhost:8080/ + +## Stopping the Application +Press `Ctrl+C` in the terminal to stop the server. + +## Troubleshooting + +**"Port already in use" error:** +Change the port in `app.py`: +```python +app.run(debug=True, host='0.0.0.0', port=8081) +``` + +**Missing modules:** +```bash +pip install -r requirements.txt +``` + +**Templates not found:** +Make sure the `templates` folder contains `index2.html` and other HTML files. + +## For Production Deployment + +See the detailed [README.md](README.md) file for deployment instructions. diff --git a/information/README.md b/information/README.md new file mode 100644 index 0000000..c772e91 --- /dev/null +++ b/information/README.md @@ -0,0 +1,171 @@ +# Product Finder Quiz - Flask Web Application + +A Python Flask web application for product selection through an interactive quiz interface. + +## Features + +- Interactive quiz with button-based and form-based questions +- Dynamic conditional logic based on user responses +- Product recommendations with images +- Responsive design +- Memory storage for user answers + +## Installation + +### Prerequisites + +- Python 3.8 or higher +- pip (Python package installer) + +### Setup + +1. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +2. **Create environment file (optional):** + ```bash + copy .env.example .env + ``` + Edit `.env` with your configuration. + +3. **Run the application:** + ```bash + python app.py + ``` + +4. **Access the application:** + Open your browser and navigate to: + - Main page: `http://localhost:8080/` + - Quiz page: `http://localhost:8080/quiz` + +## Project Structure + +``` +project/ +ā”œā”€ā”€ app.py # Main Flask application +ā”œā”€ā”€ config.py # Configuration settings +ā”œā”€ā”€ wsgi.py # WSGI entry point for production +ā”œā”€ā”€ requirements.txt # Python dependencies +ā”œā”€ā”€ templates/ # HTML templates +│ └── index2.html # Quiz page +ā”œā”€ā”€ css/ # Stylesheets +│ └── styles.css # Main stylesheet +ā”œā”€ā”€ js/ # JavaScript files +│ └── script.js # Quiz logic and data +└── images/ # Product images (optional) +``` + +## Deployment + +### Using Gunicorn (Production) + +1. **Install Gunicorn:** + ```bash + pip install gunicorn + ``` + +2. **Run with Gunicorn:** + ```bash + gunicorn -w 4 -b 0.0.0.0:8080 wsgi:app + ``` + +### Deployment on Web Panels + +#### cPanel/Plesk +1. Upload all files to your hosting directory +2. Install Python dependencies via terminal or SSH +3. Configure the web server to use Python WSGI +4. Set the application entry point to `wsgi:app` + +#### PythonAnywhere +1. Upload files or clone from repository +2. Create a new web app (Flask) +3. Set WSGI file path to `wsgi.py` +4. Install requirements in virtual environment +5. Reload the web app + +#### Heroku +1. Create `Procfile`: + ``` + web: gunicorn wsgi:app + ``` +2. Deploy using Git: + ```bash + git init + git add . + git commit -m "Initial commit" + heroku create + git push heroku main + ``` + +#### Docker +1. Create `Dockerfile`: + ```dockerfile + FROM python:3.11-slim + WORKDIR /app + COPY requirements.txt . + RUN pip install -r requirements.txt + COPY . . + CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "wsgi:app"] + ``` +2. Build and run: + ```bash + docker build -t product-finder . + docker run -p 8080:8080 product-finder + ``` + +## Configuration + +Edit `config.py` to modify application settings: +- `DEBUG`: Enable/disable debug mode +- `SECRET_KEY`: Set a secure secret key for production +- Add database URLs, API keys, etc. + +## API Endpoints + +- `GET /` - Main landing page +- `GET /quiz` - Product finder quiz +- `POST /api/save-selection` - Save user selections (for future use) +- `GET /api/get-products` - Get product data (for future use) + +## Customization + +### Adding New Products +Edit `js/script.js` and add new product entries to the `questionData` object. + +### Styling +Modify `css/styles.css` to change colors, layouts, and appearance. + +### Questions and Logic +Update the quiz flow in `js/script.js` by modifying the question structure and conditional logic. + +## Development + +Run in development mode with auto-reload: +```bash +python app.py +``` + +The application will be available at `http://localhost:8080` + +## Production Checklist + +- [ ] Set `DEBUG = False` in config +- [ ] Use a strong `SECRET_KEY` +- [ ] Configure proper database (if needed) +- [ ] Set up SSL/HTTPS +- [ ] Configure proper logging +- [ ] Set up error monitoring +- [ ] Enable CORS if needed +- [ ] Configure environment variables +- [ ] Test all routes and functionality + +## License + +Proprietary - All rights reserved + +## Support + +For issues and questions, contact your development team. diff --git a/information/REQUIRED_CODE_CHANGES.md b/information/REQUIRED_CODE_CHANGES.md new file mode 100644 index 0000000..ecbb33c --- /dev/null +++ b/information/REQUIRED_CODE_CHANGES.md @@ -0,0 +1,791 @@ +# Required Code Changes for Conditional Navigation + +## Overview +To support conditional material/color questions and dynamic navigation, the following changes are needed in your application code. + +--- + +## 1. JavaScript Changes (`js/script.js`) + +### Current State +- Loads `questions.json` statically +- Has hardcoded conditional for `q-material-conditional` +- No product data loading + +### Required Changes + +#### A. Load Multiple Data Files +```javascript +// At the top of script.js - modify loadQuestionData() to load both files +let questionData = {}; +let productData = []; +let accessoryData = []; +let bitwiseData = {}; +let accumulatedBitValue = 0; + +function init() { + // Load all data files + Promise.all([ + fetch('data/navigation.json').then(r => r.json()), + fetch('data/products.json').then(r => r.json()), + fetch('data/accessories.json').then(r => r.json()), + fetch('data/product_bitwise.json').then(r => r.json()) + ]) + .then(([questions, products, accessories, bitwise]) => { + questionData = questions; + productData = products; + accessoryData = accessories; + + // Index bitwise data by product code + bitwiseData = {}; + bitwise.forEach(item => { + bitwiseData[item.PROD_CODE] = item.BIT_VALUE; + }); + + // Check if there's state in URL + initFromURL(); + + // If no URL state, start normally + if (!window.location.search) { + loadContent('start'); + } + }) + .catch(error => { + console.error('Error loading data:', error); + // Show error message + }); +} +``` + +#### B. Add Dynamic Next Resolution Function +```javascript +// Add this new function to handle conditional navigation +function resolveNextQuestion(currentKey, selectedAnswer, answerObject) { + // Get the current accumulated filters + const currentFilters = buildFilterFromAnswers(); + + // Add the new filter from this answer + if (answerObject.filter) { + Object.assign(currentFilters, answerObject.filter); + } + + // Check if next question in chain should be shown + const nextKey = answerObject.next; + const nextQuestion = questionData[nextKey]; + + // If next question has conditional flag, evaluate it + if (nextQuestion && nextQuestion.conditional) { + return evaluateConditional(nextQuestion, currentFilters); + } + + return nextKey; +} + +// Evaluate if a conditional question should be shown +function evaluateConditional(question, filters) { + if (question.conditional.type === 'material') { + // Check if products matching current filters have multiple materials + const matchingProducts = filterProducts(filters); + const materials = getAvailableMaterials(matchingProducts); + + if (materials.length <= 1) { + // Skip this question, auto-apply material if exists + if (materials.length === 1) { + userAnswers[question.id + '.material'] = materials[0]; + } + // Return the fallback next + return question.conditional.fallbackNext || question.next; + } + } + + if (question.conditional.type === 'color') { + // Similar logic for colors + const matchingProducts = filterProducts(filters); + const colors = getAvailableColors(matchingProducts); + + if (colors.length <= 1) { + if (colors.length === 1) { + userAnswers[question.id + '.color'] = colors[0]; + } + return question.conditional.fallbackNext || question.next; + } + } + + // Show the question + return question.id; +} + +// Build current filter object from user answers +function buildFilterFromAnswers() { + const filters = {}; + + // Extract filter info from answers + for (const [key, value] of Object.entries(userAnswers)) { + if (key === 'start') { + filters.baseType = value; + } else if (key.includes('type')) { + filters.subType = value; + } else if (key.includes('material')) { + filters.material = value; + } else if (key.includes('color')) { + filters.color = value; + } + } + + return filters; +} + +// Filter products based on criteria +function filterProducts(filters) { + return productData.filter(product => { + if (filters.baseType && product.baseType !== filters.baseType) { + return false; + } + if (filters.subType) { + const subType = product.subType.door || product.subType.window; + if (subType !== filters.subType) { + return false; + } + } + if (filters.material && !product.materials.includes(filters.material)) { + return false; + } + if (filters.color && !product.colors.includes(filters.color)) { + return false; + } + return true; + }); +} + +// Get available materials from product set +function getAvailableMaterials(products) { + const materials = new Set(); + products.forEach(p => { + p.materials.forEach(m => materials.add(m)); + }); + return Array.from(materials); +} + +// Get available colors from product set +function getAvailableColors(products) { + const colors = new Set(); + products.forEach(p => { + p.colors.forEach(c => colors.add(c)); + }); + return Array.from(colors); +} +``` + +#### C. Update handleAnswer Function +```javascript +// Replace the existing handleAnswer function +function handleAnswer(currentKey, answerValue, answerIndex) { + const currentQuestion = questionData[currentKey]; + const answerObject = currentQuestion.answers[answerIndex]; + + // Store the answer + userAnswers[currentKey] = answerValue; + + // Update bit value based on selection + updateBitValue(answerObject); + + // Resolve the next question (handles conditionals) + const nextKey = resolveNextQuestion(currentKey, answerValue, answerObject); + + history.push(nextKey); + loadContent(nextKey); +} +``` + +#### D. Update renderQuestion to Pass Answer Index +```javascript +// In renderQuestion function, change the button onclick +// FROM: +// onclick="handleAnswer('${currentKey}', '${answer.caption}', '${answer.next}')" + +// TO: +data.answers.forEach((answer, index) => { + html += ` + + `; +}); +``` + +#### D2. Add URL State Management +```javascript +// Track accumulated bit value from user selections +let accumulatedBitValue = 0; + +// Load bitwise helper data +let bitwiseData = {}; + +function loadBitwiseData() { + return fetch('data/product_bitwise.json') + .then(r => r.json()) + .then(data => { + bitwiseData = {}; + data.forEach(item => { + bitwiseData[item.PROD_CODE] = item.BIT_VALUE; + }); + return bitwiseData; + }); +} + +// Initialize from URL on page load +function initFromURL() { + const params = new URLSearchParams(window.location.search); + + // Check for bit value in URL + const bitValue = params.get('b'); + if (bitValue) { + accumulatedBitValue = parseInt(bitValue, 10); + // Restore state based on bit value + restoreStateFromBitValue(accumulatedBitValue); + } + + // Check for product code in URL + const productCode = params.get('p'); + if (productCode) { + showProductByCode(productCode); + } +} + +// Update URL with current state +function updateURL() { + const params = new URLSearchParams(); + + if (accumulatedBitValue > 0) { + params.set('b', accumulatedBitValue); + } + + // Get current question + if (history.length > 0) { + const currentKey = history[history.length - 1]; + params.set('q', currentKey); + } + + // Update URL without reloading page + const newURL = window.location.pathname + '?' + params.toString(); + window.history.replaceState({ bitValue: accumulatedBitValue }, '', newURL); +} + +// Update bit value based on user selection +function updateBitValue(answerObject) { + if (answerObject.filter) { + // Add bits based on filter + if (answerObject.filter.baseType === 'Door') { + accumulatedBitValue |= 1; // Bit 0 + } else if (answerObject.filter.baseType === 'Window') { + accumulatedBitValue |= 2; // Bit 1 + } + + if (answerObject.filter.material === 'Aluminum') { + accumulatedBitValue |= 16; // Bit 4 + } else if (answerObject.filter.material === 'Vinyl') { + accumulatedBitValue |= 32; // Bit 5 + } + + // Add color bits + const colorBits = { + 'Black': 64, + 'White': 128, + 'Bronze': 256, + 'Tan': 512, + 'Mill': 1024, + 'Sandstone': 2048 + }; + + if (answerObject.filter.color && colorBits[answerObject.filter.color]) { + accumulatedBitValue |= colorBits[answerObject.filter.color]; + } + + // Add subtype bits (based on bitwise_legend.json) + const subtypeBits = { + 'Patio Door': 4096, + 'Primary Window': 8192, + 'Storm Door': 16384, + 'Storm Window': 32768 + }; + + if (answerObject.filter.subType && subtypeBits[answerObject.filter.subType]) { + accumulatedBitValue |= subtypeBits[answerObject.filter.subType]; + } + } + + updateURL(); +} + +// Restore state from bit value +function restoreStateFromBitValue(bitValue) { + // Decode bit value back to user selections + const selections = {}; + + if (bitValue & 1) selections.baseType = 'Door'; + if (bitValue & 2) selections.baseType = 'Window'; + + if (bitValue & 16) selections.material = 'Aluminum'; + if (bitValue & 32) selections.material = 'Vinyl'; + + const colors = ['Black', 'White', 'Bronze', 'Tan', 'Mill', 'Sandstone']; + const colorBits = [64, 128, 256, 512, 1024, 2048]; + + colors.forEach((color, idx) => { + if (bitValue & colorBits[idx]) { + selections.color = color; + } + }); + + // Store in userAnswers + if (selections.baseType) userAnswers['start'] = selections.baseType; + if (selections.material) userAnswers['q-material.material'] = selections.material; + if (selections.color) userAnswers['q-color.color'] = selections.color; + + // Navigate to appropriate question from URL param + const params = new URLSearchParams(window.location.search); + const questionKey = params.get('q') || 'start'; + loadContent(questionKey); +} + +// Share current state +function shareCurrentState() { + const url = window.location.href; + + // Copy to clipboard + if (navigator.clipboard) { + navigator.clipboard.writeText(url).then(() => { + alert('Link copied to clipboard! Share this link to return to this exact state.'); + }); + } else { + // Fallback: show URL + prompt('Copy this URL to share:', url); + } +} + +// Add share button to breadcrumb +function updateBreadcrumbWithShare() { + const breadcrumbDiv = document.getElementById('breadcrumb'); + const shareButton = ` + + `; + breadcrumbDiv.innerHTML += shareButton; +} +``` + +#### E. Add Product Results Page +```javascript +// Add new function to show filtered products +function showProductResults(filters) { + const matchingProducts = filterProducts(filters); + + const contentDiv = document.getElementById('content'); + + if (matchingProducts.length === 0) { + contentDiv.innerHTML = ` +
+
No Products Found
+
+ No products match your specifications. Please try different options. +
+ +
+ `; + return; + } + + let html = ` +
+
Found ${matchingProducts.length} Product(s)
+
+ `; + + matchingProducts.forEach(product => { + // Get compatible accessories + const compatibleAccessories = accessoryData.filter(acc => + product.compatibleAccessories.includes(acc.id) + ); + + html += ` +
+

${product.description}

+

Code: ${product.productCode}

+

Materials: ${product.materials.join(', ')}

+

Colors: ${product.colors.join(', ')}

+ ${compatibleAccessories.length > 0 ? ` +

Available Options:

+
    + ${compatibleAccessories.map(acc => + `
  • ${acc.description}
  • ` + ).join('')} +
+ ` : ''} +
+ `; + }); + + html += ` +
+
+ + + +
+
+ `; + + contentDiv.innerHTML = html; +} + +// Show individual product detail with URL update +function showProductDetail(productCode) { + const product = productData.find(p => p.productCode === productCode); + if (!product) return; + + // Update URL with product code + const params = new URLSearchParams(); + params.set('p', productCode); + if (accumulatedBitValue > 0) { + params.set('b', accumulatedBitValue); + } + window.history.pushState({ productCode }, '', '?' + params.toString()); + + // Get compatible accessories + const compatibleAccessories = accessoryData.filter(acc => + product.compatibleAccessories.includes(acc.id) + ); + + const contentDiv = document.getElementById('content'); + let html = ` +
+
${product.description}
+
+
+

Product Code: ${product.productCode}

+

Category: ${product.category}

+

Base Type: ${product.baseType || 'N/A'}

+

Materials: ${product.materials.join(', ') || 'N/A'}

+

Colors: ${product.colors.join(', ') || 'N/A'}

+ + ${compatibleAccessories.length > 0 ? ` +

Compatible Accessories

+
+ ${compatibleAccessories.map(acc => ` +
+ ${acc.description}
+ Code: ${acc.accessoryCode}
+ Materials: ${acc.materials.join(', ')} +
+ `).join('')} +
+ ` : ''} +
+
+
+ + + +
+
+ `; + + contentDiv.innerHTML = html; +} + +// Show product by code (from URL) +function showProductByCode(productCode) { + const product = productData.find(p => p.productCode === productCode); + if (product) { + showProductDetail(productCode); + } +} +``` + +--- + +## 2. JSON Structure Changes (`data/navigation.json`) + +### Add Conditional Metadata to Questions + +Questions that might be skipped need a `conditional` property: + +```json +{ + "q-material-storm-door": { + "type": "question", + "inputType": "button", + "title": "Select Material", + "subtitle": "Choose your preferred material", + "conditional": { + "type": "material", + "fallbackNext": "q-dimensions", + "autoApply": true + }, + "answers": [ + { + "caption": "Aluminum", + "next": "q-color", + "filter": { "material": "Aluminum" } + }, + { + "caption": "Vinyl", + "next": "q-color", + "filter": { "material": "Vinyl" } + } + ] + } +} +``` + +### Add Filter Properties to Answers + +Each answer should include filter criteria: + +```json +{ + "caption": "Storm Door", + "image": "🚪", + "next": "q-material-storm-door", + "filter": { + "baseType": "Door", + "subType": "Storm Door" + } +} +``` + +--- + +## 3. CSS Changes (`css/styles.css`) + +Add styles for product grid: + +```css +.products-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin: 20px 0; + padding: 20px; +} + +.product-card { + background: white; + border: 1px solid #ddd; + border-radius: 8px; + padding: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.product-card h3 { + margin-top: 0; + color: #333; + font-size: 1.1em; +} + +.product-card p { + margin: 10px 0; + line-height: 1.6; +} + +.product-card ul { + list-style-position: inside; + padding-left: 0; +} +``` + +--- + +## 4. Python Changes (OPTIONAL) + +If you want server-side filtering: + +```python +# Add to app.py +import json +import csv + +@app.route('/api/filter-products', methods=['POST']) +def filter_products(): + """Filter products based on criteria""" + filters = request.json + + # Load products + with open('data/products.json', 'r') as f: + products = json.load(f) + + # Apply filters + filtered = [] + for product in products: + if filters.get('baseType') and product['baseType'] != filters['baseType']: + continue + if filters.get('subType'): + sub = product['subType'].get('door') or product['subType'].get('window') + if sub != filters['subType']: + continue + if filters.get('material') and filters['material'] not in product['materials']: + continue + if filters.get('color') and filters['color'] not in product['colors']: + continue + + filtered.append(product) + + return jsonify({ + 'status': 'success', + 'count': len(filtered), + 'products': filtered + }) +``` + +--- + +## 5. CSV Parsing Script Changes + +Create a Python script to generate the JSON files: + +```python +# create_json_files.py +import csv +import json +from collections import defaultdict + +def parse_products_csv(csv_path): + """Parse products.csv and generate three JSON files""" + + products = [] + accessories = [] + nav_data = { + "start": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "answers": [] + } + } + + # Track unique values for navigation + base_types = set() + door_subtypes = defaultdict(int) # Track material diversity per subtype + window_subtypes = defaultdict(int) + + with open(csv_path, 'r', encoding='utf-8') as f: + reader = csv.DictReader(f, skipinitialspace=True) + + for row in reader: + # Skip discontinued + if row['DISCONT'].upper() == 'TRUE': + continue + + # Parse materials + materials = [] + if row['Aluminum'].upper() == 'TRUE': + materials.append('Aluminum') + if row['Vinyl'].upper() == 'TRUE': + materials.append('Vinyl') + + # Parse colors + colors = [] + for color in ['Black', 'White', 'Bronze', 'Tan', 'Mill', 'Sandstone']: + if row.get(color, '').upper() == 'TRUE': + colors.append(color) + + # Determine if accessory + is_accessory = row['Accessory\\nYes'].upper() == 'TRUE' + + # Build sub-type object + sub_type = { + "door": row['Sub-type\\nDoor'].strip() if row['Sub-type\\nDoor'] else None, + "window": row['Sub-type\\nWindow'].strip() if row['Sub-type\\nWindow'] else None + } + + # Common data + item_data = { + "id": row['PROD_CODE'], + "productCode": row['PROD_CODE'], + "category": row['CATEGORY'], + "description": row['DESCRIPTION'], + "discontinued": False, + "location": row['LOC_CODE'], + "baseType": row['Base\\nType'].strip() if row['Base\\nType'] else None, + "subType": sub_type, + "materials": materials, + "colors": colors + } + + if is_accessory: + # Add to accessories + accessories.append({ + **item_data, + "compatibilityRules": { + "type": "subType", + "subTypeDoor": sub_type['door'], + "subTypeWindow": sub_type['window'], + # ... more rules + } + }) + else: + # Add to products + products.append({ + **item_data, + "isAccessory": False, + "compatibleAccessories": [] + }) + + # Track for navigation + if item_data['baseType']: + base_types.add(item_data['baseType']) + + # Build navigation structure + # ... (logic to create navigation.json based on analysis) + + # Write JSON files + with open('data/products.json', 'w') as f: + json.dump(products, f, indent=2) + + with open('data/accessories.json', 'w') as f: + json.dump(accessories, f, indent=2) + + with open('data/navigation.json', 'w') as f: + json.dump(nav_data, f, indent=2) + +if __name__ == '__main__': + parse_products_csv('data/products.csv') + print("JSON files generated successfully!") +``` + +--- + +## Implementation Checklist + +- [ ] Update `js/script.js` with conditional navigation logic +- [ ] Create parsing script to generate JSON files from CSV +- [ ] Run parsing script to create `navigation.json`, `products.json`, `accessories.json` +- [ ] Update CSS for product display +- [ ] Test navigation flow with different product types +- [ ] Verify material questions are skipped when appropriate +- [ ] Verify color questions are skipped when appropriate +- [ ] Test product filtering and results display +- [ ] Add accessory display on product pages + +--- + +## Testing Scenarios + +1. **Storm Door Selection**: Should skip material question (all Aluminum) +2. **Window Selection**: Should show material question (mixed materials) +3. **Single Color Products**: Should skip color question +4. **Multi-Color Products**: Should show color question +5. **Product Results**: Should show filtered products with compatible accessories + +--- + +## Summary + +**CSV**: No changes needed (unless adding bit values) +**JavaScript**: Major refactoring for conditional logic +**JSON**: New structure with filter objects and conditional flags +**Python**: Optional parsing script to generate JSON from CSV diff --git a/information/START_HERE.md b/information/START_HERE.md new file mode 100644 index 0000000..814c8ff --- /dev/null +++ b/information/START_HERE.md @@ -0,0 +1,184 @@ +# šŸŽ‰ Flask Web Application Successfully Created! + +Your HTML/JavaScript quiz has been converted to a Python Flask web application. + +## āœ… What Was Created + +### Core Application Files +- **app.py** - Main Flask application with routes +- **wsgi.py** - Production WSGI entry point +- **config.py** - Configuration management +- **requirements.txt** - Python dependencies + +### Templates & Static Files +- **templates/** - HTML files (index.html, index2.html, 404.html) +- **css/** - Stylesheets (styles.css) +- **js/** - JavaScript files (script.js) +- **images/** - Product images folder + +### Documentation & Utilities +- **README.md** - Complete documentation +- **QUICKSTART.md** - Quick start guide +- **run.bat** - Windows startup script +- **.gitignore** - Git ignore file +- **.env.example** - Environment template + +## šŸš€ Quick Start + +### Option 1: Using Batch File (Windows) +Double-click `run.bat` to install dependencies and start the server. + +### Option 2: Manual Start +```bash +# Install dependencies +pip install -r requirements.txt + +# Run the application +python app.py +``` + +### Access Your Application +Open in browser: **http://localhost:8080/quiz** + +## 🌐 Current Status + +āœ… Flask is currently running on: http://localhost:8080 +āœ… Quiz page: http://localhost:8080/quiz +āœ… Debug mode: Enabled (auto-reloads on file changes) + +## šŸ“ Project Structure + +``` +project/ +ā”œā”€ā”€ app.py ← Main Flask app (START HERE) +ā”œā”€ā”€ wsgi.py ← For production deployment +ā”œā”€ā”€ config.py ← Settings & configuration +ā”œā”€ā”€ requirements.txt ← Python packages needed +ā”œā”€ā”€ run.bat ← Windows startup script +│ +ā”œā”€ā”€ templates/ ← HTML files (Flask requires this folder) +│ ā”œā”€ā”€ index.html ← Main landing page +│ ā”œā”€ā”€ index2.html ← Quiz page +│ └── 404.html ← Error page +│ +ā”œā”€ā”€ css/ ← Stylesheets +│ └── styles.css ← Main CSS file +│ +ā”œā”€ā”€ js/ ← JavaScript files +│ └── script.js ← Quiz logic and data +│ +└── images/ ← Product images (add your images here) +``` + +## šŸŽÆ Key Features + +āœ… **Dynamic Routing** - Flask handles all page requests +āœ… **Static File Serving** - CSS, JS, and images properly served +āœ… **Error Handling** - Custom 404 page +āœ… **API Endpoints** - Ready for future backend features +āœ… **Production Ready** - WSGI config included +āœ… **All Original Features** - Quiz, forms, conditional logic, memory storage + +## šŸ”§ Customization + +### Change Port +Edit `app.py`, line with `app.run()`: +```python +app.run(debug=True, host='0.0.0.0', port=YOUR_PORT) +``` + +### Add New Routes +In `app.py`: +```python +@app.route('/your-page') +def your_page(): + return render_template('your-page.html') +``` + +### Update Quiz Questions +Edit `js/script.js` - modify the `questionData` object + +### Change Styling +Edit `css/styles.css` + +## šŸ“¦ Deployment Options + +### 1. Web Panels (cPanel, Plesk) +- Upload all files +- Install requirements: `pip install -r requirements.txt` +- Point to `wsgi.py` + +### 2. Cloud Platforms +- **Heroku**: Add `Procfile` and push to Git +- **PythonAnywhere**: Upload and configure WSGI +- **AWS/Azure**: Use with Gunicorn + +### 3. Docker +See README.md for Dockerfile example + +### 4. Production Server +```bash +pip install gunicorn +gunicorn -w 4 -b 0.0.0.0:8080 wsgi:app +``` + +## šŸ› Troubleshooting + +### Port Already in Use +Change port in app.py or kill the process: +```bash +# Windows +netstat -ano | findstr :8080 +taskkill /PID /F +``` + +### Templates Not Found +Make sure HTML files are in `templates/` folder + +### Static Files Not Loading +Check that `css/` and `js/` folders exist in root directory + +### Module Not Found +```bash +pip install -r requirements.txt +``` + +## šŸ“š Next Steps + +1. **Test the application** - Visit http://localhost:8080/quiz +2. **Add your product images** - Place images in `images/` folder +3. **Customize the quiz** - Edit `js/script.js` +4. **Update styling** - Modify `css/styles.css` +5. **Deploy** - Follow README.md deployment guide + +## šŸ”’ Security Notes for Production + +Before deploying to production: +- [ ] Set `DEBUG = False` in config.py +- [ ] Change `SECRET_KEY` to a strong random value +- [ ] Use environment variables for sensitive data +- [ ] Set up HTTPS/SSL +- [ ] Use a production WSGI server (Gunicorn, uWSGI) +- [ ] Configure proper logging +- [ ] Set up database backups (if using a database) + +## šŸ’” Tips + +- Flask auto-reloads when you edit files (in debug mode) +- Press `Ctrl+C` to stop the server +- Check terminal for error messages +- Use browser DevTools to debug JavaScript +- All original quiz functionality is preserved + +## šŸ“ž Need Help? + +- Check **README.md** for detailed documentation +- Check **QUICKSTART.md** for simple instructions +- Review Flask logs in terminal for errors +- Test API endpoints using browser or Postman + +--- + +**Your Flask app is ready to use! šŸŽŠ** + +Visit: http://localhost:8080/quiz diff --git a/information/SUBDIRECTORY_DEPLOYMENT.md b/information/SUBDIRECTORY_DEPLOYMENT.md new file mode 100644 index 0000000..3ffbc65 --- /dev/null +++ b/information/SUBDIRECTORY_DEPLOYMENT.md @@ -0,0 +1,311 @@ +# Subdirectory Deployment Guide + +This guide explains how to deploy the CGW Product Finder to a subdirectory on your web server (e.g., `http://example.com/cgwproducts/`). + +## šŸŽÆ Overview + +The application is now configured to support subdirectory deployments through the `APPLICATION_ROOT` configuration variable. All templates use relative paths and `url_for()` to ensure proper routing regardless of deployment location. + +## šŸ› ļø Configuration + +### Method 1: Environment Variable (Recommended for Production) + +Set the `APPLICATION_ROOT` environment variable before starting the application: + +**Linux/Mac (Apache with Passenger):** +```bash +export APPLICATION_ROOT="/cgwproducts" +``` + +**Windows (IIS):** +Add to web.config or set in IIS environment variables: +``` +APPLICATION_ROOT=/cgwproducts +``` + +**Apache .htaccess or Virtual Host:** +```apache +SetEnv APPLICATION_ROOT /cgwproducts +``` + +### Method 2: Modify config.py + +Edit `app/config.py` and change the APPLICATION_ROOT line: + +```python +class Config: + SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-here' + + # Change this line to your subdirectory path + APPLICATION_ROOT = '/cgwproducts' # Or whatever your path is +``` + +**Important:** The path should: +- Start with `/` +- NOT end with `/` +- Match your web server configuration + +### Method 3: Server Configuration + +#### Apache with Passenger + +If deploying to `http://example.com/cgwproducts/`: + +```apache + + ServerName example.com + + # Document root is one level up from app folder + DocumentRoot /path/to/CGW Product Finder + + # Set subdirectory as alias to app folder + Alias /cgwproducts /path/to/CGW Product Finder/app + + + Allow from all + Options -MultiViews + + # Set APPLICATION_ROOT environment variable + SetEnv APPLICATION_ROOT /cgwproducts + + # Enable Passenger + PassengerEnabled on + PassengerAppRoot /path/to/CGW Product Finder/app + PassengerPython /path/to/python3 + + + # Block access to admin and data folders + + Require all denied + + + + # Allow access only through Flask API + + Require all denied + + + +``` + +#### Nginx with uWSGI + +```nginx +server { + listen 80; + server_name example.com; + + location /cgwproducts { + # Strip the /cgwproducts prefix when passing to Flask + rewrite ^/cgwproducts(.*)$ $1 break; + + include uwsgi_params; + uwsgi_pass unix:/tmp/cgw-product-finder.sock; + + # Set APPLICATION_ROOT + uwsgi_param APPLICATION_ROOT /cgwproducts; + } + + # Block admin folder + location /cgwproducts/admin { + deny all; + } +} +``` + +## šŸ“ Python 3.13.11 Considerations + +For **Python 3.13.11**, some packages may not have pre-built wheels yet. + +### Updated requirements.txt + +The requirements.txt has been updated to make Pillow optional: + +```txt +Flask>=3.0.0 +Werkzeug>=3.0.0 +# Pillow>=10.0.0 # Optional - only for image generation +``` + +### Installation Steps + +```bash +# Upgrade pip first +python -m pip install --upgrade pip + +# Install core requirements (will work without Pillow) +pip install Flask>=3.0.0 Werkzeug>=3.0.0 + +# Try to install Pillow (optional) +pip install Pillow + +# If Pillow fails, the app will still work but disable image generation +``` + +**Note:** Pillow build errors on Python 3.13.11 are common. If you don't need dynamic image generation, you can skip it. + +## āœ… Testing Your Deployment + +### Step 1: Verify Configuration + +Check that APPLICATION_ROOT is set correctly: + +```python +# Run in Python console +import os +print(os.environ.get('APPLICATION_ROOT', '/')) +``` + +### Step 2: Test Routes + +If deployed to `/cgwproducts/`, test these URLs: + +- āœ… `http://example.com/cgwproducts/` → Should redirect to login +- āœ… `http://example.com/cgwproducts/login` → Should show login page +- āœ… `http://example.com/cgwproducts/api/session` → Should return JSON +- āœ… `http://example.com/cgwproducts/css/styles.css` → Should load CSS +- āœ… `http://example.com/cgwproducts/js/script.js` → Should load JS +- āœ… `http://example.com/cgwproducts/data/products.json` → Should load data + +### Step 3: Test Login Flow + +1. Go to `/cgwproducts/login` +2. Login with Master / Master +3. Should redirect to `/cgwproducts/select-location` +4. Select a location +5. Should redirect to `/cgwproducts/` +6. User info should display in header +7. Click logout +8. Should return to `/cgwproducts/login` + +### Step 4: Test User Management + +1. Login as Master +2. On location selection page, click "User Management" +3. Should go to `/cgwproducts/users` +4. All buttons should work (Add User, Change Password, etc.) + +## šŸ”§ Troubleshooting + +### Issue: 404 on CSS/JS files + +**Cause:** Static files not being served correctly + +**Fix:** Ensure Flask routes for /css/, /js/, /data/ are working: + +```bash +# Test directly +curl http://example.com/cgwproducts/css/styles.css +curl http://example.com/cgwproducts/js/script.js +``` + +### Issue: Login redirects to wrong path + +**Cause:** APPLICATION_ROOT not set or incorrect + +**Fix:** +1. Check environment variable: `echo $APPLICATION_ROOT` +2. Verify it matches your URL path +3. Restart web server after changing + +### Issue: API calls return 404 + +**Cause:** API routes need APPLICATION_ROOT prefix + +**Fix:** All templates now use `BASE_URL` variable: +```javascript +const BASE_URL = '{{ base_url }}'; // Automatically set by Flask +fetch(BASE_URL + '/api/login', {...}) +``` + +### Issue: Cannot access /users or other protected pages + +**Cause:** Session not persisting across requests + +**Fix:** +1. Verify SECRET_KEY is set and doesn't change between restarts +2. Check cookie settings (SESSION_COOKIE_PATH should match APPLICATION_ROOT) +3. Ensure browser accepts cookies from subdirectory + +### Issue: Module import errors after pip install + +**Cause:** Pillow build failed on Python 3.13.11 + +**Fix:** Pillow is now optional. Application will work without it: +```bash +# Install without Pillow +pip install Flask>=3.0.0 Werkzeug>=3.0.0 + +# App will show: "Image generation not available" +# But all other features work +``` + +## šŸ“‚ Files Modified for Subdirectory Support + +The following files have been updated to support subdirectory deployments: + +### Backend: +- `app/config.py` - Added APPLICATION_ROOT configuration +- `app/app.py` - Added context processor for base_url +- `app/requirements.txt` - Made Pillow optional + +### Templates (use {{ base_url }} and {{ url_for() }}): +- `app/templates/login.html` +- `app/templates/select_location.html` +- `app/templates/user_manager.html` +- `app/templates/index2.html` +- `app/templates/access_denied.html` + +### JavaScript Updates: +All templates now define `BASE_URL` at the top of their scripts: +```javascript +const BASE_URL = '{{ base_url }}'; +``` + +All fetch calls use: `fetch(BASE_URL + '/api/endpoint', ...)` + +## šŸš€ Deployment Checklist + +Before deploying to a subdirectory: + +- [ ] Set APPLICATION_ROOT environment variable or update config.py +- [ ] Install Flask and Werkzeug: `pip install Flask>=3.0.0 Werkzeug>=3.0.0` +- [ ] (Optional) Install Pillow: `pip install Pillow` +- [ ] Upload all modified files to server +- [ ] Configure web server (Apache/Nginx) with subdirectory path +- [ ] Set secure SECRET_KEY in production +- [ ] Block public access to /admin/ and /data/ folders +- [ ] Test all routes with subdirectory prefix +- [ ] Test login flow and session persistence +- [ ] Verify CSS/JS/images load correctly +- [ ] Test API endpoints return correct responses + +## šŸ“ž Need Help? + +Common deployment paths: +- Root: `/` (default, no configuration needed) +- Application subdirectory: `/cgwproducts` +- User subdirectory: `/~username/cgwproducts` +- Domain subdirectory: `/app` + +Whatever path you choose, set it as APPLICATION_ROOT and ensure your web server passes requests to Flask with that prefix. + +## šŸ” Security Notes + +When deploying to a subdirectory: + +1. **SECRET_KEY** - Must be set and persistent across restarts +2. **Session Cookies** - Will be scoped to the subdirectory path +3. **Admin Folder** - Must be blocked from web access +4. **Data Folder** - JSON files should only be accessible through API +5. **HTTPS** - Use SSL/TLS in production and set SESSION_COOKIE_SECURE = True + +## ✨ Benefits of This Approach + +- āœ… Deploy to any path without code changes +- āœ… Works at root `/` or subdirectory `/cgwproducts/` +- āœ… All routes automatically adjust to deployment path +- āœ… No hardcoded URLs in templates or JavaScript +- āœ… Compatible with Apache, Nginx, IIS +- āœ… Passenger and uWSGI compatible +- āœ… Works with Python 3.13.11 (Pillow optional) diff --git a/information/TROUBLESHOOTING_404.md b/information/TROUBLESHOOTING_404.md new file mode 100644 index 0000000..1ff8e7b --- /dev/null +++ b/information/TROUBLESHOOTING_404.md @@ -0,0 +1,294 @@ +# Troubleshooting Guide - 404 Errors & Installation Issues + +## šŸ”“ Issue 1: pip install requirements.txt fails + +### Error Message: +``` +error: subprocess-exited-with-error +Ɨ Getting requirements to build wheel did not run successfully. +``` + +### Solution: + +This error usually happens with **Pillow** on Windows systems that don't have build tools installed. + +#### Option 1: Install Pre-built Pillow (Recommended) +```bash +# Upgrade pip first +python -m pip install --upgrade pip + +# Install Pillow from pre-built wheels +pip install --upgrade Pillow + +# Then install other requirements +pip install Flask>=3.0.0 +pip install Werkzeug>=3.0.0 +``` + +#### Option 2: Install from updated requirements.txt +The requirements.txt has been updated to use version ranges instead of exact versions: +```bash +pip install -r requirements.txt +``` + +#### Option 3: Skip Pillow (if you don't need image generation) +If you don't need the dynamic image generation feature, you can install without Pillow: +```bash +pip install Flask>=3.0.0 +pip install Werkzeug>=3.0.0 +``` + +The app will still work - it will just disable the image generation features. + +--- + +## šŸ”“ Issue 2: Login Page Returns 404 Error + +### Common Causes & Solutions: + +### Cause 1: Flask Server Not Running +**Check if the server is running:** +```bash +# Navigate to app folder +cd "C:\Users\Work\Desktop\CGW Product Finder\app" + +# Run the Flask app +python app.py +``` + +You should see: +``` +āœ“ Image generation available + * Running on http://127.0.0.1:8080 +``` + +**Then access:** http://localhost:8080/login + +--- + +### Cause 2: Wrong URL or Port +**Common mistakes:** + +āŒ `http://localhost/login` - Missing port number +āœ… `http://localhost:8080/login` - Correct + +āŒ `http://localhost:5000/login` - Wrong port +āœ… `http://localhost:8080/login` - Correct (app uses port 8080) + +āŒ `/login` in browser address bar +āœ… `http://localhost:8080/login` - Need full URL + +--- + +### Cause 3: Flask App Import Error +**Test if routes are registered:** +```bash +cd app +python test_routes.py +``` + +This will show: +- If Flask imports successfully +- All registered routes including /login +- What went wrong if there's an error + +**Expected output:** +``` +āœ“ Flask app imported successfully + +šŸ“ Login & Authentication Routes: + /login [GET] -> login_page + /api/login [POST] -> login + /api/logout [POST] -> logout +``` + +--- + +### Cause 4: Web Server Configuration (Apache/Passenger/IIS) +If you're running through a web server instead of `python app.py`: + +**Check your server configuration:** + +For **Apache + Passenger:** +- Verify passenger_wsgi.py is being used +- Check if Python path is correct in config +- Ensure the app folder is set as DocumentRoot + +For **IIS:** +- Verify web.config is correct +- Check if Python handler is configured +- Ensure proper app folder path + +**Test directly first:** +Always test with `python app.py` first to verify the app works before troubleshooting web server issues. + +--- + +## šŸ“‹ Step-by-Step Troubleshooting + +### Step 1: Verify Installation +```bash +# Check Python version (need 3.7+) +python --version + +# Check if Flask is installed +python -c "import flask; print(flask.__version__)" + +# Check if Werkzeug is installed +python -c "import werkzeug; print(werkzeug.__version__)" +``` + +### Step 2: Fresh Install +```bash +cd "C:\Users\Work\Desktop\CGW Product Finder\app" + +# Upgrade pip +python -m pip install --upgrade pip + +# Install requirements +pip install -r requirements.txt + +# Or manual install +pip install Flask>=3.0.0 Werkzeug>=3.0.0 Pillow>=10.0.0 +``` + +### Step 3: Test Routes +```bash +cd app +python test_routes.py +``` + +Expected: List of routes including /login + +### Step 4: Start Server +```bash +python app.py +``` + +Expected output: +``` +āœ“ Image generation available + * Running on http://127.0.0.1:8080 + * Running on http://192.168.x.x:8080 +``` + +### Step 5: Access in Browser +Open browser: http://localhost:8080/login + +Expected: Login page with username/password fields + +--- + +## šŸ› Common Errors & Fixes + +### Error: "ModuleNotFoundError: No module named 'flask'" +```bash +pip install Flask>=3.0.0 +``` + +### Error: "ModuleNotFoundError: No module named 'werkzeug'" +```bash +pip install Werkzeug>=3.0.0 +``` + +### Error: "Address already in use" / "Port 8080 is already in use" +```bash +# Find what's using port 8080 +netstat -ano | findstr :8080 + +# Kill the process (replace PID with actual process ID) +taskkill /PID /F + +# Or edit app.py to use different port (line 911): +app.run(debug=True, host='0.0.0.0', port=8090) +``` + +### Error: "Template Not Found: login.html" +```bash +# Verify template exists +dir templates\login.html + +# If missing, the file needs to be uploaded to the server +``` + +### Error: "Working outside of application context" +This means Flask app isn't initialized properly. Run `python test_routes.py` to diagnose. + +--- + +## āœ… Verification Checklist + +After fixing issues, verify: + +- [ ] Flask server starts without errors: `python app.py` +- [ ] http://localhost:8080/ redirects to http://localhost:8080/login āœ“ +- [ ] http://localhost:8080/login shows login page āœ“ +- [ ] Can login with username: `Master` password: `Master` āœ“ +- [ ] After login, see location selection page āœ“ +- [ ] Can access user management (Master user only) āœ“ +- [ ] Logout works and returns to login page āœ“ + +--- + +## šŸš€ Production Deployment + +For production servers (not localhost): + +1. **Use proper WSGI server** (not `python app.py`) + - Passenger (Apache/Nginx) + - uWSGI + - Gunicorn (Linux) + +2. **Set secure SECRET_KEY** in config.py + ```python + # Don't use secrets.token_hex() in production + SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secure-random-key-here' + ``` + +3. **Enable HTTPS** and update config: + ```python + SESSION_COOKIE_SECURE = True # Only send cookies over HTTPS + ``` + +4. **Set Debug=False** for production + +--- + +## šŸ“ž Still Having Issues? + +1. Run the test script: + ```bash + python test_routes.py + ``` + +2. Check Flask server output for errors: + ```bash + python app.py + ``` + Look for red error messages + +3. Test with curl: + ```bash + curl http://localhost:8080/login + ``` + Should return HTML, not 404 + +4. Check browser console for JavaScript errors (F12) + +5. Verify file permissions (server can read templates/) + +--- + +## šŸ“ Files Needed for Login System + +Ensure these files exist and are uploaded: +- āœ… app/app.py +- āœ… app/templates/login.html +- āœ… app/templates/user_manager.html +- āœ… app/templates/access_denied.html +- āœ… app/templates/select_location.html +- āœ… app/templates/index2.html +- āœ… app/css/styles.css +- āœ… app/data/users.json + +Missing any of these will cause 404 or errors. diff --git a/information/URL_EXAMPLES.md b/information/URL_EXAMPLES.md new file mode 100644 index 0000000..871a413 --- /dev/null +++ b/information/URL_EXAMPLES.md @@ -0,0 +1,352 @@ +# URL State Example - Navigation Flow + +## šŸ“ Visual Journey Through URL Changes + +### Step 1: Page Load +``` +URL: https://yoursite.com/ +State: Fresh start, no parameters +Bit Value: 0 +``` +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ What are you looking for? │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā” │ +│ │ 🚪 │ │ 🪟 │ │ +│ │Door │ │Window│ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +--- + +### Step 2: User Selects "Door" +``` +URL: https://yoursite.com/?b=1&q=q-door-type + ↑ + Bit 0 set (Door) +State: Door selected +Bit Value: 1 (binary: 1) +Bits Active: Door +``` +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ What type of door? │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │Storm Door│ │Patio Door│ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +--- + +### Step 3: User Selects "Storm Door" +``` +URL: https://yoursite.com/?b=16385&q=q-material-storm-door + ↑ + 16385 = 1 (Door) + 16384 (Storm Door) +State: Door + Storm Door selected +Bit Value: 16385 (binary: 100000000000001) +Bits Active: Door, Storm Door subtype + +Calculation: + Bit 0 (Door) = 1 + Bit 14 (Storm Door)= 16384 + Total = 16385 +``` +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Select Material │ +│ CONDITIONAL: Only showing │ +│ because some storm doors have │ +│ multiple materials │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │Aluminum │ │ Vinyl │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +--- + +### Step 4: User Selects "Aluminum" +``` +URL: https://yoursite.com/?b=16401&q=q-color-storm-door + ↑ + 16401 = 1 + 16 + 16384 +State: Door + Aluminum + Storm Door +Bit Value: 16401 (binary: 100000000010001) +Bits Active: Door, Aluminum, Storm Door subtype + +Calculation: + Bit 0 (Door) = 1 + Bit 4 (Aluminum) = 16 + Bit 14 (Storm Door)= 16384 + Total = 16401 +``` +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Select Color │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ Black │ │ White │ │Bronze │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +--- + +### Step 5: User Selects "White" +``` +URL: https://yoursite.com/?b=16529&q=q-dimensions + ↑ + 16529 = 1 + 16 + 128 + 16384 +State: Door + Aluminum + White + Storm Door +Bit Value: 16529 (binary: 100000010010001) +Bits Active: Door, Aluminum, White, Storm Door + +Calculation: + Bit 0 (Door) = 1 + Bit 4 (Aluminum) = 16 + Bit 7 (White) = 128 + Bit 14 (Storm Door)= 16384 + Total = 16529 +``` +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Enter Dimensions │ +│ Width: [______] inches │ +│ Height: [______] inches │ +│ [Continue →] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +--- + +### Step 6: View Results +``` +URL: https://yoursite.com/?b=16529&q=results +State: Showing filtered products +Bit Value: 16529 +Products Matched: All doors that are: + āœ“ Storm Door type (bit 14) + āœ“ Aluminum material (bit 4) + āœ“ Available in White (bit 7) +``` +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Found 3 Products │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ STAR 6100 FULL VIEW │ │ +│ │ Code: 6100I │ │ +│ │ Aluminum, White, Bronze │ │ +│ │ [View Details] │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ COLUMBIA COBRA │ │ +│ │ Code: COBRAI │ │ +│ │ [View Details] │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +--- + +### Step 7: Click Product Detail +``` +URL: https://yoursite.com/?p=6100I&b=16529 + ↑ + Product code added +State: Viewing specific product 6100I +Bit Value: 16529 (preserved for "back" navigation) +``` +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ STAR 6100 FULL VIEW STORM DOOR │ +│ ────────────────────────────────── │ +│ Product Code: 6100I │ +│ Category: STD │ +│ Materials: Aluminum │ +│ Colors: White, Bronze │ +│ │ +│ Compatible Accessories: │ +│ • BGIST - Inserts for Storm Doors │ +│ • TGIODD - Top Glass Inserts │ +│ │ +│ [← Back] [Start Over] [šŸ”— Share] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +--- + +### Step 8: Copy & Share URL +``` +User clicks "šŸ”— Share" button +Copies: https://yoursite.com/?p=6100I&b=16529 + +Someone else pastes this URL in their browser: +→ Instantly shows product 6100I +→ Navigation state preserved (b=16529) +→ Back button navigates through original path +``` + +--- + +### Step 9: Browser Back Button +``` +User clicks browser back button + +URL Changes: +https://yoursite.com/?p=6100I&b=16529 + ↓ +https://yoursite.com/?b=16529&q=results + ↓ +https://yoursite.com/?b=16529&q=q-dimensions + ↓ +https://yoursite.com/?b=16401&q=q-color-storm-door + ↓ +https://yoursite.com/?b=16385&q=q-material-storm-door + ↓ +https://yoursite.com/?b=1&q=q-door-type + ↓ +https://yoursite.com/ + +Each step backward: +āœ“ Restores question state +āœ“ Maintains bit value +āœ“ Shows correct UI +``` + +--- + +### Step 10: Refresh Page +``` +At any point, user presses F5 to refresh + +Before Refresh: https://yoursite.com/?b=16529&q=q-dimensions +After Refresh: https://yoursite.com/?b=16529&q=q-dimensions + +Result: +āœ“ Returns to same question +āœ“ Previous answers restored +āœ“ Accumulated selections preserved +āœ“ Can continue where they left off +``` + +--- + +## šŸŽÆ Bit Value Breakdown Reference + +### Common Values You'll See + +| Bit Value | Decimal | What It Means | +|-----------|---------|---------------| +| `0000...0001` | 1 | Door selected | +| `0000...0010` | 2 | Window selected | +| `0001...0001` | 16385 | Door + Storm Door | +| `0001...0011` | 16387 | Door + Window + Storm Door | +| `0001...0011` | 16401 | Door + Aluminum + Storm Door | +| `0010...0010` | 16529 | Door + Aluminum + White + Storm Door | + +### Quick Decode Formula +``` +To check if a bit is set: + if (bitValue & (1 << bitPosition)) { + // That attribute is selected + } + +Examples: + 16529 & 1 = 1 → Door is selected āœ“ + 16529 & 2 = 0 → Window is NOT selected + 16529 & 16 = 16 → Aluminum is selected āœ“ + 16529 & 128 = 128 → White is selected āœ“ + 16529 & 16384 = 16384 → Storm Door is selected āœ“ +``` + +--- + +## šŸ”— URL Patterns + +### Navigation State +``` +Pattern: /?b={bitValue}&q={questionKey} +Example: /?b=16529&q=q-dimensions +Use: Bookmark navigation progress +``` + +### Product View +``` +Pattern: /?p={productCode}&b={bitValue} +Example: /?p=6100I&b=16529 +Use: Direct link to product with context +``` + +### Initial State +``` +Pattern: / +Example: https://yoursite.com/ +Use: Fresh start, no parameters +``` + +--- + +## šŸ’” Power User Features + +### Hack the URL +Users can manually modify URLs: + +``` +Original: /?b=16529&q=q-dimensions + (Door + Aluminum + White + Storm Door) + +Modified: /?b=34&q=q-dimensions + (Vinyl only) + +Result: Jumps directly to Vinyl products +``` + +### Preset Configurations +Your sales team can create bookmarks: + +``` +Residential Storm Doors: + /?b=16401&q=results + +Commercial Windows: + /?b=8194&q=results + +Budget Options (Vinyl): + /?b=32&q=start +``` + +### Analytics Tracking +Track which combinations are popular: + +``` +Top URLs: + /?b=16529 → White Aluminum Storm Doors (2,453 views) + /?b=8226 → White Vinyl Windows (1,834 views) + /?b=272 → Bronze products (892 views) +``` + +--- + +## šŸŽ“ Learning Example + +### Try This Exercise: +1. Start at homepage (b=0) +2. Select Window (b=2) +3. Select Primary Window (b=8194) +4. Select Vinyl (b=8226) +5. Select White (b=8354) + +Your URL should be: `/?b=8354&q=q-dimensions` + +Decode: 8354 in binary = 10000010100010 +- Bit 1 (2) = Window āœ“ +- Bit 5 (32) = Vinyl āœ“ +- Bit 7 (128) = White āœ“ +- Bit 13 (8192) = Primary Window āœ“ +Total: 2 + 32 + 128 + 8192 = 8354 āœ“ + +--- + +Ready to implement! šŸš€ diff --git a/information/URL_IMPLEMENTATION_SUMMARY.md b/information/URL_IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..f9473f8 --- /dev/null +++ b/information/URL_IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,230 @@ +# Implementation Summary - URL State Management + +## šŸŽÆ What You Get + +Your application will support **shareable URLs** that preserve: +- āœ… User's navigation progress (bitwise value) +- āœ… Current question position +- āœ… Direct product links +- āœ… Browser refresh without data loss +- āœ… Browser back/forward buttons +- āœ… Copy/share functionality + +## šŸ”— URL Examples + +``` +# Initial state (no params) +https://yoursite.com/ + +# After selecting Door + Storm Door + Aluminum +https://yoursite.com/?b=16401&q=q-dimensions + ↑ ↑ + | Current question + Accumulated bit value (Door + Aluminum + Storm Door) + +# Viewing specific product +https://yoursite.com/?p=BGIST&b=16401 + ↑ + Product code + +# Bit value 16401 decodes to: +# Bit 0 (1) = Door +# Bit 4 (16) = Aluminum +# Bit 14 (16384) = Storm Door subtype +# Total: 1 + 16 + 16384 = 16401 +``` + +## šŸ“‹ Implementation Checklist + +### Phase 1: Core URL Functionality (Essential) +- [ ] Add bitwise data loading to `init()` function +- [ ] Add `accumulatedBitValue` variable +- [ ] Add `BIT_DEFINITIONS` constants +- [ ] Implement `updateURL()` function +- [ ] Implement `updateBitValue()` function +- [ ] Update `handleAnswer()` to call `updateBitValue()` +- [ ] Implement `initFromURL()` function +- [ ] Implement `restoreStateFromBitValue()` function +- [ ] Update `startOver()` to clear URL + +### Phase 2: Product Deep Linking (Recommended) +- [ ] Implement `showProductByCode()` function +- [ ] Update `showProductDetail()` to update URL +- [ ] Make product cards clickable +- [ ] Add URL params when viewing products + +### Phase 3: Share Functionality (Nice to Have) +- [ ] Implement `shareCurrentPage()` function +- [ ] Add `showNotification()` helper +- [ ] Add "Share" buttons to UI +- [ ] Add CSS for notification animations + +### Phase 4: Browser Navigation (Polish) +- [ ] Add `popstate` event listener +- [ ] Test browser back button +- [ ] Test browser forward button +- [ ] Test refresh behavior + +## šŸš€ Quick Start + +### Step 1: Add Global Variables +Add to top of `js/script.js`: +```javascript +let accumulatedBitValue = 0; +let bitwiseData = {}; + +const BIT_DEFINITIONS = { + 'base_door': 0, 'base_window': 1, + 'material_aluminum': 4, 'material_vinyl': 5, + 'color_black': 6, 'color_white': 7, 'color_bronze': 8, + 'color_tan': 9, 'color_mill': 10, 'color_sandstone': 11, + 'subtype_patio_door': 12, 'subtype_primary_window': 13, + 'subtype_storm_door': 14, 'subtype_storm_window': 15 +}; +``` + +### Step 2: Load Bitwise Data +Update `init()` to load `product_bitwise.json`: +```javascript +Promise.all([ + fetch('data/navigation.json').then(r => r.json()), + fetch('data/products.json').then(r => r.json()), + fetch('data/accessories.json').then(r => r.json()), + fetch('data/product_bitwise.json').then(r => r.json()) // ADD THIS +]) +.then(([questions, products, accessories, bitwise]) => { + // ... existing code ... + + // Index bitwise data + bitwiseData = {}; + bitwise.forEach(item => { + bitwiseData[item.PROD_CODE] = item.BIT_VALUE; + }); + + // Check for URL state + initFromURL(); +}); +``` + +### Step 3: Add URL Functions +Copy these three key functions from [URL_STATE_MANAGEMENT.md](URL_STATE_MANAGEMENT.md): +1. `updateURL()` - Updates browser URL +2. `updateBitValue()` - Calculates bit value from selection +3. `initFromURL()` - Restores state from URL on load + +### Step 4: Update handleAnswer +Add one line to `handleAnswer()`: +```javascript +function handleAnswer(currentKey, answerValue, answerIndex) { + // ... existing code ... + + updateBitValue(answerObject); // ADD THIS LINE + + const nextKey = resolveNextQuestion(currentKey, answerValue, answerObject); + history.push(nextKey); + + updateURL(nextKey); // ADD THIS LINE + + loadContent(nextKey); +} +``` + +### Step 5: Test +1. Run your app +2. Navigate through questions +3. Check URL updates after each selection +4. Copy URL and paste in new tab +5. Should restore to same state āœ… + +## šŸ“– Documentation Files + +All details are in these files: +- **[URL_STATE_MANAGEMENT.md](URL_STATE_MANAGEMENT.md)** - Complete implementation guide +- **[REQUIRED_CODE_CHANGES.md](REQUIRED_CODE_CHANGES.md)** - Updated with URL features +- **[BITWISE_USAGE_GUIDE.md](BITWISE_USAGE_GUIDE.md)** - How bitwise system works + +## šŸ”§ Key Functions Reference + +| Function | Purpose | When Called | +|----------|---------|-------------| +| `initFromURL()` | Read URL params on page load | Once at startup | +| `updateURL()` | Write current state to URL | After each answer | +| `updateBitValue()` | Add selection to bit value | After each answer | +| `restoreStateFromBitValue()` | Decode bit value to selections | On page load from URL | +| `shareCurrentPage()` | Copy URL to clipboard | User clicks "Share" | + +## šŸŽØ URL Format Design + +### Why Bitwise? +- **Compact**: `?b=16401` vs `?door=true&aluminum=true&storm=true` +- **Fast**: Single integer comparison +- **Flexible**: Easy to add new attributes +- **Shareable**: Short URLs +- **Reversible**: Can decode back to selections + +### Parameters Chosen +- `b` = bit value (short, recognizable) +- `q` = question (short, clear purpose) +- `p` = product (short, clear purpose) + +### Alternative Considered +Could use hash fragments instead: +``` +https://yoursite.com/#/door/storm-door/aluminum +``` +But query params are better for: +- Server-side rendering +- Analytics tracking +- SEO (if products are indexed) + +## āš ļø Important Notes + +1. **Bit Definitions Must Match**: The `BIT_DEFINITIONS` in JavaScript must match the Python script +2. **URL Length Limits**: URLs have practical limits (~2000 chars), but bit values are small +3. **No Sensitive Data**: Don't put sensitive info in URL (it's visible and loggable) +4. **Test Thoroughly**: Test all navigation paths and browser actions + +## šŸ› Troubleshooting + +### URL Not Updating +- Check `updateURL()` is called after `handleAnswer()` +- Check browser console for errors +- Verify `accumulatedBitValue` is being set + +### State Not Restoring +- Check `initFromURL()` is called in `init()` +- Verify URL has `b` and `q` parameters +- Check `restoreStateFromBitValue()` logic + +### Wrong Bit Values +- Verify `BIT_DEFINITIONS` matches `generate_bitwise_helper.py` +- Check `bitwise_legend.json` for correct bit positions +- Use browser console: `console.log(accumulatedBitValue)` + +### Share Button Not Working +- Check clipboard API support: `navigator.clipboard` +- Fallback to `prompt()` for older browsers +- Test in HTTPS (clipboard API requires secure context) + +## šŸ“ˆ Benefits Summary + +| Feature | User Benefit | Business Benefit | +|---------|-------------|------------------| +| Shareable URLs | Share configurations | Viral marketing | +| Bookmarks | Save favorites | Return visitors | +| Refresh-safe | No data loss | Better UX | +| Deep linking | Direct to product | SEO indexing | +| Browser nav | Back/forward works | Expected behavior | +| Short URLs | Easy to share | More sharing | + +## šŸŽÆ Next Steps + +1. āœ… Implement Phase 1 (core URL functionality) +2. āœ… Test basic URL state restoration +3. āœ… Add Phase 2 (product deep linking) +4. āœ… Add Phase 3 (share buttons) +5. āœ… Add Phase 4 (browser navigation) +6. āœ… Test all scenarios thoroughly +7. āœ… Add analytics tracking (optional) + +Good luck! šŸš€ diff --git a/information/URL_STATE_MANAGEMENT.md b/information/URL_STATE_MANAGEMENT.md new file mode 100644 index 0000000..6da1366 --- /dev/null +++ b/information/URL_STATE_MANAGEMENT.md @@ -0,0 +1,514 @@ +# URL State Management Implementation + +## Overview +This feature allows users to: +1. **Bookmark** their progress through the navigation flow +2. **Refresh** the page without losing their selections +3. **Share** URLs with specific products or navigation states +4. **Deep link** directly to products + +## URL Parameter Structure + +### Query Parameters +- `b` - Bitwise value representing all selections (integer) +- `q` - Current question ID (string) +- `p` - Product code for direct product view (string) + +### Examples +``` +# At start +https://yoursite.com/ + +# After selecting Door > Storm Door > Aluminum +https://yoursite.com/?b=16401&q=q-dimensions + +# Viewing a specific product +https://yoursite.com/?p=BGIST&b=16401 + +# Bit value 16401 decodes to: +# - Door (bit 0) +# - Aluminum (bit 4) +# - Storm Door subtype (bit 14) +``` + +## Implementation Code + +### 1. Add to Global Variables (top of script.js) +```javascript +// Add these to existing global variables +let accumulatedBitValue = 0; +let bitwiseData = {}; + +// Bit definitions (must match generate_bitwise_helper.py) +const BIT_DEFINITIONS = { + // Base Types + 'base_door': 0, // 1 + 'base_window': 1, // 2 + + // Flags + 'is_accessory': 2, // 4 + 'specific_item': 3, // 8 + + // Materials + 'material_aluminum': 4, // 16 + 'material_vinyl': 5, // 32 + + // Colors + 'color_black': 6, // 64 + 'color_white': 7, // 128 + 'color_bronze': 8, // 256 + 'color_tan': 9, // 512 + 'color_mill': 10, // 1024 + 'color_sandstone': 11, // 2048 + + // Subtypes (from bitwise_legend.json) + 'subtype_patio_door': 12, // 4096 + 'subtype_primary_window': 13, // 8192 + 'subtype_storm_door': 14, // 16384 + 'subtype_storm_window': 15 // 32768 +}; +``` + +### 2. Update init() Function +```javascript +function init() { + // Load all data files (including bitwise) + Promise.all([ + fetch('data/navigation.json').then(r => r.json()), + fetch('data/products.json').then(r => r.json()), + fetch('data/accessories.json').then(r => r.json()), + fetch('data/product_bitwise.json').then(r => r.json()) + ]) + .then(([questions, products, accessories, bitwise]) => { + questionData = questions; + productData = products; + accessoryData = accessories; + + // Index bitwise data by product code + bitwiseData = {}; + bitwise.forEach(item => { + bitwiseData[item.PROD_CODE] = item.BIT_VALUE; + }); + + // Initialize from URL or start fresh + initFromURL(); + }) + .catch(error => { + console.error('Error loading data:', error); + showError('Failed to load application data.'); + }); +} +``` + +### 3. URL Initialization +```javascript +function initFromURL() { + const params = new URLSearchParams(window.location.search); + + // Priority 1: Direct product view + const productCode = params.get('p'); + if (productCode) { + accumulatedBitValue = parseInt(params.get('b') || '0', 10); + showProductByCode(productCode); + return; + } + + // Priority 2: Restore navigation state + const bitValue = params.get('b'); + const questionKey = params.get('q'); + + if (bitValue && questionKey) { + accumulatedBitValue = parseInt(bitValue, 10); + restoreStateFromBitValue(accumulatedBitValue, questionKey); + return; + } + + // Priority 3: Start fresh + loadContent('start'); +} + +function restoreStateFromBitValue(bitValue, questionKey) { + // Decode bit value back to user selections + + // Base type + if (bitValue & 1) { + userAnswers['start'] = 'Door'; + } else if (bitValue & 2) { + userAnswers['start'] = 'Window'; + } + + // Materials + if (bitValue & 16) { + userAnswers['q-material.material'] = 'Aluminum'; + } else if (bitValue & 32) { + userAnswers['q-material.material'] = 'Vinyl'; + } + + // Colors + const colorMap = { + 64: 'Black', + 128: 'White', + 256: 'Bronze', + 512: 'Tan', + 1024: 'Mill', + 2048: 'Sandstone' + }; + + for (const [bit, color] of Object.entries(colorMap)) { + if (bitValue & parseInt(bit)) { + userAnswers['q-color.color'] = color; + break; // Only store first color found + } + } + + // Subtypes + const subtypeMap = { + 4096: 'Patio Door', + 8192: 'Primary Window', + 16384: 'Storm Door', + 32768: 'Storm Window' + }; + + for (const [bit, subtype] of Object.entries(subtypeMap)) { + if (bitValue & parseInt(bit)) { + userAnswers['q-subtype'] = subtype; + break; // Only store first subtype found + } + } + + // Navigate to the saved question + history = ['start']; + if (questionKey !== 'start') { + history.push(questionKey); + } + loadContent(questionKey); +} +``` + +### 4. Update URL on Each Selection +```javascript +function updateURL(questionKey = null) { + const params = new URLSearchParams(); + + if (accumulatedBitValue > 0) { + params.set('b', accumulatedBitValue.toString()); + } + + if (questionKey) { + params.set('q', questionKey); + } else if (history.length > 0) { + params.set('q', history[history.length - 1]); + } + + const newURL = window.location.pathname + (params.toString() ? '?' + params.toString() : ''); + window.history.replaceState( + { + bitValue: accumulatedBitValue, + questionKey: questionKey + }, + '', + newURL + ); +} + +function updateBitValue(answerObject) { + if (!answerObject.filter) return; + + const filter = answerObject.filter; + + // Base type + if (filter.baseType === 'Door') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['base_door']); + } else if (filter.baseType === 'Window') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['base_window']); + } + + // Materials + if (filter.material === 'Aluminum') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['material_aluminum']); + } else if (filter.material === 'Vinyl') { + accumulatedBitValue |= (1 << BIT_DEFINITIONS['material_vinyl']); + } + + // Colors + const colorKey = filter.color ? 'color_' + filter.color.toLowerCase() : null; + if (colorKey && BIT_DEFINITIONS[colorKey]) { + accumulatedBitValue |= (1 << BIT_DEFINITIONS[colorKey]); + } + + // Subtypes + const subtypeKey = filter.subType ? 'subtype_' + filter.subType.toLowerCase().replace(/ /g, '_') : null; + if (subtypeKey && BIT_DEFINITIONS[subtypeKey]) { + accumulatedBitValue |= (1 << BIT_DEFINITIONS[subtypeKey]); + } +} +``` + +### 5. Update handleAnswer Function +```javascript +function handleAnswer(currentKey, answerValue, answerIndex) { + const currentQuestion = questionData[currentKey]; + const answerObject = currentQuestion.answers[answerIndex]; + + // Store the answer + userAnswers[currentKey] = answerValue; + + // Update bit value based on selection + updateBitValue(answerObject); + + // Resolve the next question (handles conditionals) + const nextKey = resolveNextQuestion(currentKey, answerValue, answerObject); + + history.push(nextKey); + + // Update URL with new state + updateURL(nextKey); + + loadContent(nextKey); +} +``` + +### 6. Product View with URL +```javascript +function showProductByCode(productCode) { + const product = productData.find(p => p.productCode === productCode); + if (!product) { + showError(`Product ${productCode} not found`); + return; + } + + showProductDetail(product); +} + +function showProductDetail(product) { + // Update URL with product code + const params = new URLSearchParams(); + params.set('p', product.productCode); + if (accumulatedBitValue > 0) { + params.set('b', accumulatedBitValue.toString()); + } + + window.history.pushState( + { productCode: product.productCode }, + '', + '?' + params.toString() + ); + + // Render product detail + const compatibleAccessories = accessoryData.filter(acc => + product.compatibleAccessories && product.compatibleAccessories.includes(acc.id) + ); + + const contentDiv = document.getElementById('content'); + let html = ` +
+
${product.description}
+
+
+

Product Code: ${product.productCode}

+

Category: ${product.category}

+

Base Type: ${product.baseType || 'N/A'}

+

Materials: ${product.materials.join(', ') || 'N/A'}

+

Colors: ${product.colors.join(', ') || 'N/A'}

+ + ${compatibleAccessories.length > 0 ? ` +

Compatible Accessories

+
+ ${compatibleAccessories.map(acc => ` +
+ ${acc.description}
+ Code: ${acc.accessoryCode} | ${acc.materials.join(', ')} +
+ `).join('')} +
+ ` : ''} +
+
+
+ + + +
+
+ `; + + contentDiv.innerHTML = html; +} +``` + +### 7. Share Functionality +```javascript +function shareCurrentPage() { + const url = window.location.href; + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(url).then(() => { + showNotification('Link copied to clipboard!'); + }).catch(() => { + showUrlPrompt(url); + }); + } else { + showUrlPrompt(url); + } +} + +function showUrlPrompt(url) { + const message = prompt('Copy this URL to share:', url); +} + +function showNotification(message) { + const notification = document.createElement('div'); + notification.className = 'notification'; + notification.textContent = message; + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #4CAF50; + color: white; + padding: 15px 20px; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + z-index: 10000; + animation: slideIn 0.3s ease-out; + `; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.style.animation = 'slideOut 0.3s ease-in'; + setTimeout(() => notification.remove(), 300); + }, 3000); +} +``` + +### 8. Update startOver Function +```javascript +function startOver() { + history = ['start']; + + // Clear all stored answers + for (const key in userAnswers) { + delete userAnswers[key]; + } + + // Clear bit value + accumulatedBitValue = 0; + + // Clear URL (return to root) + window.history.replaceState({}, '', window.location.pathname); + + loadContent('start'); +} +``` + +### 9. Handle Browser Back Button +```javascript +// Add this to init() or as separate event listener +window.addEventListener('popstate', function(event) { + if (event.state) { + if (event.state.productCode) { + showProductByCode(event.state.productCode); + } else if (event.state.questionKey) { + accumulatedBitValue = event.state.bitValue || 0; + loadContent(event.state.questionKey); + } else { + startOver(); + } + } else { + // No state, check URL + initFromURL(); + } +}); +``` + +### 10. Add CSS for Notification +```css +@keyframes slideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(400px); + opacity: 0; + } +} + +.accessory-item { + padding: 10px; + margin: 5px 0; + background: #f5f5f5; + border-left: 3px solid #2196F3; + border-radius: 3px; +} + +.product-card { + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; +} + +.product-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.2); +} +``` + +## Testing Scenarios + +### Test 1: Basic Navigation +1. Start quiz +2. Select Door → Storm Door → Aluminum → White +3. Check URL contains `?b=XXXX&q=YYYY` +4. Copy URL +5. Open in new tab → Should restore to same point + +### Test 2: Product Deep Link +1. Navigate to product BGIST +2. Check URL contains `?p=BGIST&b=XXXX` +3. Copy URL +4. Open in new tab → Should show product directly + +### Test 3: Browser Refresh +1. Navigate through several questions +2. Press F5 to refresh +3. Should restore to same question with selections intact + +### Test 4: Browser Back Button +1. Navigate forward through questions +2. Press browser back button +3. Should step backward through questions +4. URL should update accordingly + +### Test 5: Share Button +1. Complete navigation flow +2. Click "Share" button +3. Should copy URL to clipboard +4. Paste in new browser → Should restore state + +## Benefits + +1. **User Experience**: Users don't lose progress on refresh +2. **Shareability**: Users can share specific configurations +3. **Bookmarking**: Useful configurations can be saved +4. **SEO**: Product pages are directly linkable +5. **Analytics**: Track specific navigation paths via URL parameters +6. **Support**: Users can share URLs when asking for help + +## URL Encoding Notes + +- Bit values are stored as decimal integers (more compact than hex for small values) +- Product codes are URL-safe (no special encoding needed) +- Question IDs are alphanumeric (q-dimensions, etc.) +- Special characters in product codes should be URL-encoded if present diff --git a/information/USER_MANAGEMENT_README.md b/information/USER_MANAGEMENT_README.md new file mode 100644 index 0000000..fde44e7 --- /dev/null +++ b/information/USER_MANAGEMENT_README.md @@ -0,0 +1,343 @@ +# User Management System + +## Overview +A secure user management front-end that allows you to create and manage users. Each user record contains: +- **Username**: Unique identifier +- **Password**: Securely hashed using PBKDF2-SHA256 (industry-standard) +- **Default Location**: Primary location (required) - one of: Lindsborg, Iola, KC, or BMD +- **Active Status**: Whether the user account is active or inactive (toggleable in user list) +- **Location Settings**: Per-location configuration with: + - **Accessible**: Whether the user can access this location + +## User Interface Features + +### Table-Based Location Configuration +The form uses an intuitive table layout with: +- **Default Location Column**: Radio buttons to select ONE primary location (required) +- **Accessible Column**: Checkboxes to mark which locations the user can access + +### User List with Active Toggle +Each user in the list shows: +- **Username** and **Location Information** +- **Active/Inactive Toggle**: Click to enable or disable the user account + - Active users have full border color + - Inactive users are dimmed with reduced opacity +- **Delete Button**: Remove the user permanently + +### Smart Header Checkboxes +The accessible column has a header checkbox that: +- Shows three states: checked āœ“, unchecked ☐, or indeterminate ⊟ (mixed) +- **Clicking cycles**: If off or mixed → all on, if on → all off +- **Auto-updates**: When you check/uncheck individual rows, the header shows: + - āœ“ if all are checked + - ☐ if none are checked + - ⊟ if some are checked (mixed state) + +### Extensible Design +The table structure is designed to easily add more columns in the future: +- Permission columns (Permission 1, Permission 2, etc.) +- Custom attributes +- Feature flags +- Any other per-location settings + +Each new column can have the same header checkbox behavior. + +## Security Features +- **Password Hashing**: Passwords are hashed using `pbkdf2:sha256` algorithm +- **Not Plain Text**: Passwords are never stored in plain text +- **Cryptographically Secure**: Uses Werkzeug's secure password hashing +- **Cannot Be Decoded**: Hashed passwords cannot be reversed back to plain text + +## How to Use + +### 1. Access the User Manager +Navigate to: `http://localhost:8080/users` + +### 2. Add New Users +- Fill in the form with username, password, and location +- Click "Add User" button +- User will be added with a securely hashed password + +### 3. View Users +- All users are displayed in a list showing username and location +- Password hashes are NOT displayed for security + +### 4. Download Users JSON +- Click "šŸ“„ Download Users JSON" button +- Downloads a `users.json` file containing all users +- Password field contains the secure hash (not plain text) + +### 5. Delete Users +- Click "Delete" next to any user to remove them +- Click "šŸ—‘ļø Clear All Users" to remove all users at once + +## API Endpoints + +### GET /users +Displays the user management interface. + +### GET /api/users +Returns all users in JSON format. + +**Response:** +```json +{ + "status": "success", + "users": [ + { + "username": "john_doe", + "password": "pbkdf2:sha256:600000$...", + "defaultLocation": "LINDS", + "active": true, + "locationSettings": { + "LINDS": { "accessible": true }, + "IOLA": { "accessible": true }, + "KC": { "accessible": false }, + "BMD": { "accessible": false } + } + } + ] +} +``` + +### POST /api/users +Adds a new user. + +**Request Body:** +```json +{ + "username": "jane_smith", + "password": "mySecurePassword123", + "defaultLocation": "KC", + "active": true, + "locationSettings": { + "LINDS": { "accessible": true }, + "IOLA": { "accessible": true }, + "KC": { "accessible": true }, + "BMD": { "accessible": true } + } +} +``` + +**Response:** +```json +{ + "status": "success", + "message": "User added successfully", + "users": [...] +} +``` + +### DELETE /api/users/{index} +Deletes a user by their index position. + +### PATCH /api/users/{index}/active +Toggle user active status. + +**Request Body:** +```json +{ + "active": true +} +``` + +### DELETE /api/users/clear +Clears all users from the system. + +### GET /api/users/download +Downloads all users as a JSON file. + +## Password Security + +### How Passwords Are Stored +Passwords are hashed using PBKDF2-SHA256 with the following properties: +- **Algorithm**: PBKDF2 (Password-Based Key Derivation Function 2) +- **Hash Function**: SHA-256 +- **Iterations**: 600,000+ (computationally expensive for attackers) +- **Salt**: Automatically generated unique salt per password + +### Example Hash Format +``` +pbkdf2:sha256:600000$AbCdEfGh$1234567890abcdef... +``` + +Components: +- `pbkdf2:sha256` - Algorithm identifier +- `600000` - Number of iterations +- `$AbCdEfGh` - Random salt +- `$1234567890abcdef...` - Actual hash + +### Password Verification +To verify a password, use Werkzeug's `check_password_hash()`: + +```python +from werkzeug.security import check_password_hash + +# user['password'] contains the hash +if check_password_hash(user['password'], provided_password): + print("Password is correct!") +``` + +## Data Storage +Users are stored in: `app/data/users.json` + +**Example users.json:** +```json +[ + { + "username": "admin", + "password": "pbkdf2:sha256:600000$r7K8L9M0$a1b2c3d4e5f6...", + "defaultLocation": "LINDS", + "active": true, + "locationSettings": { + "LINDS": { "accessible": true }, + "IOLA": { "accessible": true }, + "KC": { "accessible": false }, + "BMD": { "accessible": false } + } + }, + { + "username": "user1", + "password": "pbkdf2:sha256:600000$n5O6P7Q8$x9y8z7w6v5u4...", + "defaultLocation": "KC", + "active": false, + "locationSettings": { + "LINDS": { "accessible": true }, + "IOLA": { "accessible": true }, + "KC": { "accessible": true }, + "BMD": { "accessible": true } + } + } +] +``` + +### Location Codes +- **LINDS** = Lindsborg +- **IOLA** = Iola +- **KC** = KC +- **BMD** = BMD + +## Integration Example + +### Authenticate and Get User Info + +```python +from werkzeug.security import check_password_hash +import json + +def authenticate_user(username, password): + """Authenticate a user by username and password""" + with open('app/data/users.json', 'r') as f: + users = json.load(f) + + # Find user + user = next((u for u in users if u['username'] == username), None) + + if user and check_password_hash(user['password'], password): + return True, user + return False, None + +# Usage +success, user_data = authenticate_user('john_doe', 'password123') +if success: + print(f"Welcome {user_data['username']}!") + print(f"Default location: {user_data['defaultLocation']}") + print(f"Account active: {user_data.get('active', True)}") + + # Check if user can access a location + if user_data['locationSettings']['KC']['accessible']: + print("User can access KC") +``` + +### Check User Permissions for a Location + +```python +def can_access_location(user_data, location_code): + """Check if user can access a specific location""" + return user_data.get('locationSettings', {}).get(location_code, {}).get('accessible', False) + +def is_user_active(user_data): + """Check if user account is active""" + return user_data.get('active', True) + +# Usage +if not is_user_active(user_data): + print("User account is inactive") + return + +if can_access_location(user_data, 'LINDS'): + print("User can access Lindsborg") +``` + +### Get All Accessible Locations for a User + +```python +def get_accessible_locations(user_data): + """Get all locations where user has access""" + accessible_locations = [] + for location_code, settings in user_data.get('locationSettings', {}).items(): + if settings.get('accessible', False): + accessible_locations.append(location_code) + return accessible_locations + +# Usage +accessible = get_accessible_locations(user_data) +print(f"User can access: {', '.join(accessible)}") +``` + +## Notes +- Username must be unique +- Minimum password length: 6 characters +- Default location is required (one of: Lindsborg, Iola, KC, BMD) +- **Default location is automatically marked as accessible** when creating a user +- Accessible checkboxes are optional for other locations +- New users are **active by default** +- Toggle active status in the user list section +- Users are stored locally in JSON format +- This is a separate endpoint from the main Product Finder app + +## Adding New Permissions/Columns + +The system is designed to be easily extensible. To add new permission columns: + +### 1. Update the HTML table +Add a new column header and cells in `user_manager.html`: + +```html + + + + Permission Name + + + + + + +``` + +### 2. Update the JavaScript form submission +Modify the form submission handler to collect the new permission: + +```javascript +locationSettings[loc.code] = { + active: activeCheckbox ? activeCheckbox.checked : false, + accessible: accessibleCheckbox ? accessibleCheckbox.checked : false, + newPermission: newPermCheckbox ? newPermCheckbox.checked : false // Add this +}; +``` + +### 3. Update the backend (optional) +The backend already handles any properties in `locationSettings`, so no changes are required unless you want validation. + +### 4. Reset header checkbox on form submit +Add to the form reset section: + +```javascript +document.getElementById('headerNewPermission').checked = false; +document.getElementById('headerNewPermission').indeterminate = false; +``` + +That's it! The system will automatically save and load the new permission data. diff --git a/information/deprecated/Procfile b/information/deprecated/Procfile new file mode 100644 index 0000000..8b56fd3 --- /dev/null +++ b/information/deprecated/Procfile @@ -0,0 +1 @@ +web: gunicorn wsgi:app diff --git a/information/deprecated/codes.text b/information/deprecated/codes.text new file mode 100644 index 0000000..b27ba10 --- /dev/null +++ b/information/deprecated/codes.text @@ -0,0 +1,262 @@ +PROD_CODE CATEGORY DESCRIPTION +-------------------------------------------------------------------------------- +1400 SHPW SERIES 1400 VINYL SLIDING PRIMARY +1400SCR SHPW 1400 SCREEN +1500 SHPT SERIES 1500 S.H. TILT VINYL PRIMARY +1510 FPPW 1510 VINYL INSULATED FIXED LITE +1650 SHPW #1650 INSULATED SINGLE HUNG +1650-10 FPPW #1650-10 INSULATED FIXED LITE +1650SCR SCREENS SCREENS FOR #1650 +1650VS SHPW 1650 BOTTOM SASH +1700 SHPW #1700 INSULATED SLIDER +1700CSC SCREENS SCREEN FOR #1700 CENTER VENT SLIDER +1700CV SHPW 1700 CENTER VENT +1700ESC SCREENS SCREEN FOR #1700 ENDS VENT SLIDER +1700EV SHPW 1700 END VENT +1700SCR SCREENS SCREEN FOR #1700 SLIDER +1700VS SHPW #1700 VENT SASH +1710 FPPW C-1710 FIXED LITE +2000 SHPT SERIES 2000 S.H. T.B. TILT PRIMARY +2000SCR SHPT C-2000 SCREEN +2100 SHPT SERIES 2100 THERMAL BREAK SLIDER +2100EV 2000SLI SERIES 2100 3-PANEL SLIDER +2200 FPPW SERIES 2200 T.B. FIXED LITE +2200PDG FPPW SERIES 2200 TB FIXED LITE W/TEMP GLASS +2400 PD 2400 ROYAL CROWN PATIO DOOR +2650 SHPW #2650 SINGLE HUNG SINGLE GLAZED PRIME +265010 FPPW #2650-10 SINGLE GLAZED FIXED LITE +2650SCR SCREENS SCREENS FOR 2650 +2650VP SHPW #2650 VENT PANEL - SINGLE HUNGE +2700 SHPW #2700 SINGLE GLAZED SLIDER +2700CV SHPW C-2700 - CENTER VENT +2700EV SHPW C-2700 - END VENT +2700SCR SCREENS SCREENS FOR #2700 +2700VS SHPW #2700 VENT SASH +2710 FPPW C-2710 FIXED LITE - SINGLE GLAZED +3000 3000DHP SERIES 3000 D.H. T.B REPLACEMENT WD +3000SCR SCREENS SCREENS FOR #3000 +303 STORMS #303 DART STORM WINDOWS +305INS INSERTS #305 SASH WINDCHECK INSERTS +306INS INSERTS #306 SCHLEGEL GLASS INSERTS +3100 3000DHP SERIES 3100 T.B. REPLACEMENT SLIDER +3100EV 3000DHP SERIES 3100 3-PANEL SLIDER +3100SCR SCREENS SCREENS FOR #3100 +310PSCR SCRINS #310 PLAIN SCREENS +3200 3000FPP SERIES 3200 T.B. PICTURE WINDOW +3300 CASEMNT 3300 1-PANEL T.B. CASEMENT +3302 CASEMNT 3300 2-PANEL T.B. CASEMENT +3303 CASEMNT 3300 3-PANEL T.B. CASEMENT +3310 CASEMNT 3310 FIXED CASEMENT +3400 CASEMNT 3400 1 PANEL VINYL CLAD CASEMENT +3402 CASEMNT 3400 2 PANEL VINYL CLAD CASEMENT +3403 CASEMNT 3400 3 PANEL VINYL CLAD CASEMENT +350 STORMS #350 ARROW STORM WINDOWS +3500 CASEMNT 3500 1-PANEL WOOD INTERIOR CASEMENT +3502 CASEMNT 3500 2-PANEL WOOD INTERIOR CASEMENT +3503 CASEMNT 3503 3-PANEL WOOD INTERIOR CASEMENT +3700 SHPW #3700 INSULATED SLIDER +3700SCR SCREENS SCREEN FOR #3700 POLE BARN WD +3700VP SHPW #3700 VENT PANEL +3710 FPPW C-3710 INSULATED FIXED LITE +404 STORMS #404 FALCON STORM WINDOWS +404ONE STPW #404 ONE-LITE ST WD +4100 REPLACE SERIES 4100 VINYL SLIDER +450 STORMS #450 RAVEN STORM WINDOWS +450ONE STPW #450 ONE-LITE ST WD +4700 SHPW #4700 SINGLE GLAZED SLIDER +4700SCR SCREENS SCREENS FOR #4700 +4710 FPPW #4710 SINGLE GLAZED FIXED LITE +5000SCR SCREENS SCREEN FOR R5000 SLIDER +5200 PD 5200 VINYL PATIO DOORS +5700 SHPW #5700 INSULATED SLIDER +606 STORMS #606 LION STORM WINDOWS +606ONE STPW #606 ONE-LITE ST WD +6100I STD STAR 6100 FULL VIEW STORM DOOR +6100L STD STAR 6100 FULL VIEW STORM DOOR +6300 SHPT SERIES 6300 S.H. TILT VINYL PRIMARY +6301 SHPW SERIES 6301 VINYL SLIDING PRIMARY +650 STORMS #650 LYNX STORM WINDOWS +650ONE STPW #650 ONE-LITE ST WD +6700 SHPW #6700 SINGLE GLAZED SLIDER +7100I STD STAR 7100 FULL VIEW SELF STORING DOOR +7100L STD STAR 7100 FULL VIEW SELF STORING DOOR +808 STORMS 808 HAWK STORM WINDOW +808ONE STPW #808 ONE-LITE ST WD +8100I STD STAR 8100 FULL VIEW STORM DOOR +8100L STD STAR 8100 FULL VIEW STORM DOOR +850 STORMS #850 KENT STORM WINDOWS +850ONE STPW #850 ONE-LITE ST WD +BELMONT 3000FPP BELMONT ALLIANCE DOUBLE HUNG WINDOW +BGI INSERTS BOTTOM GL INSERTS FOR ECONOMY ST WDS +BGI404 INSERTS BOTTOM GL INSERTS FOR #404/450 ST WDS +BGI606 INSERTS BOTTOM GL INSERTS FOR #606/650 ST WDS +BGIST INSERTS INSERTS FOR STORM DOORS +C-500 SHPW C-500 SH THERMAL BREAK INS. +C1150 CASEMNT C1150-VINYL AWNING WINDOW +C1500AR CIR TOP C-1510 VINYL ARCH TOP-OPERATING WINDOW +C1510AR CIR TOP C-1510 VINYL ARCH TOP WINDOW +C1521 CIR TOP C-1521 VINYL CIRCLE TOP +C1526 CIR TOP C-1526 VINYL CIRCLE TOP +C1621 CIR TOP C-1621 INSULATED CIRCLE TOP +C1622 CIR TOP C-1622 INSULATED CIRCLE TOP +C1626VA CIR TOP C-1626VA INSULATED CIRCLE TOPS +C1710 SHPW #1710 PICTURE OVER SLIDER +C1721 CIR TOP C-1721 INSULATED CIRCLE TOPS +C1722 CIR TOP C-1722 INSULATED CIRCLE TOPS +C1724 CIR TOP C-1724 INSULATED CIRCLE TOPS +C1800 C1800 C-1800 INSIDE SLIDING STORM WINDOW +C2021 CIR TOP C-2021 T.B CIRCLE TOPS +C2022 CIR TOP C-2022 T.B. CIRCLE TOPS +C2026 CIR TOP C-2026 T.B. CIRCLE TOPS +C2710 SHPW #2710 PICTURE OVER SLIDER +C300 BSMT C-300 ALUM INSERTS FOR BASEMENT BUCKS +C3221 CIR TOP C-3221 T.B CIRCLE TOPS +C3222 CIR TOP C-3222 T.B. CIRCLE TOPS +C3223 CIR TOP C-3223 T.B. CIRCLE TOPS +C3224 CIR TOP C-3224 T.B. CIRCLE TOPS +C3300 CASEMNT 3300 1-PANEL T.B. CASEMENT +C3700 SHPW C-3700 INSULATED SLIDING POLE BARN WND +C400 BSMT C-400 VINYL INSERT FOR BASEMENT BUCKS +C4000 REPLACE C-4000 SINGLE HUNGE PRIMARY WINDOW +C4260 PD C-4260 STEEL MIRROR DOOR +C500 SHPW C-500 INS THERMAL BREAK SINGLE HUNG +C500GL VENTS LITES OF INSULATED GLASS FOR C-500'S +C500GLP VENTS LITES OF INSULATED GLASS FOR C-500'S +C500PP SHPW C-500 INS THERMAL BREAK SINGLE HUNG +C500SCR SCREENS C-500 SCREEN +C500VP VENTS VENT PANELS FOR C-500 +C521 CIR TOP C-521 T.B. CIRCLE TOPS +C522 CIR TOP C-522 T.B. CIRCLE TOPS +C526 CIR TOP C-526 T.B. CIRCLE TOPS +C610 CASEMNT C610 VINYL CASEMENT WINDOW +C620 FPPW C620 VINYL AWNING WINDOWS +C621 CIR TOP C-621 VINYL CIRCLE TOP +C626 CIR TOP C-626 VINYL CIRCLE TOP +C640 FPPW C-640 VINYL FIXED CASEMENT WINDOW +C826 CIR TOP C-826 VINYL CIRCLE TOP +C828 CIR TOP C-828 VINYL CIRCLE TOP +C8321 CIR TOP C-8321 VINYL CIRCLE TOP +C8326 CIR TOP C-8326 VINYL CIRCLE TOP +C900 SHPW C-900 INS THERMAL BREAK SLIDER +C900EV SHPW C-900 ENDS VENT SLIDER +C900SCR SCREENS SCREEN FOR C-900 +C910 FPPW C-910 INSULATED T.B. FIXED LITE +C910PP FPPW C-910 ARCH TOP +C921 CIR TOP C-921 T.B. CIRCLE TOPS +C922 CIR TOP C-922 T.B. CIRCLE TOPS +C924 CIR TOP C-924 T.B. CIRCLE TOPS +C931 CIR TOP C-931 T.B. ROUND PRIMARY WINDOW +C939 CIR TOP C-939 T.B. ROUND PRIMARY WINDOW +C940 CIR TOP C-940 T.B. OCTAGON PRIMARY +C949 CIR TOP C949 OCTAGON THERMAL BREAK WINDOW +C960 FPPW C-960 INSULATED T.B. CIRCLE TOP +COBRAI STD COLUMBIA COBRA STORM DOOR +COBRAL STD COLUMBIA COBRA STORM DOOR +COBRATI STD COLUMBIA COBRA STORM DOOR +COBRATL STD COLUMBIA COBRA STORM DOOR +CRWNFVI STD CROWN FULL VIEW STORM DOOR +CRWNFVL STD CROWN FULL VIEW STORM DOOR +CRWNSDI STD COLUMBIA CROWN SCREEN DOOR +CRWNSDL STD COLUMBIA CROWN SCREEN DOOR +D770 SHPT D770 D.H. TILT VINYL PRIMARY WINDOWS +D780 SHPW D780 DOUBLE SLIDE VINYL PRIMARY +D830 SHPT D830 D.H.TILT VINYL PRIMARY WINDOWS +D830SCR SHPT D830 SCREEN +D832 FPPW D832 FIXED VINYL PRIMARY WINDOWS +DSGLASS GLASS DOUBLE STRENGTH GLASS +DURASEA FPPW DURASEAL 5/8" (GRAY) PER REEL +EXPAND EXPANDR SILL EXPANDERS +FULLSCR SCREENS FULL SCREEN +FV10I STD KING FV-10 DECORATOR STORM DOOR +FV10L STD KING FV-10 DECORATOR STORM DOOR +FV3I STD KING FV-3 DECORATOR STORM DOOR +FV3L STD KING FV-3 DECORATOR STORM DOOR +FVGI INSERTS GLASS INSERTS FOR KING ONE LITE +FVSI INSERTS FULL SCREENS ONLY FOR KING ONE-LITES +G3000 GARDEN COLUMBIA COMFORT 3000 VINYL GARDEN WD +GOLIATH STD GOLIATH STORM DOOR +HERCULE STD HERCULES STORM DOOR +IMPERIL PD IMPERIAL PATIO DOORS +INSGLAS GLASS INSULATED GLASS +ISP PD STATIONARY PANELS FOR IMPERIAL DOORS +IVP PD VENT PANELS FOR IMPERIAL PATIO DOORS +JET PD COLUMBIA JET PATIO DOORS +JSP PD STATIONARY PANELS FOR JET DOORS +JVP PD VENT PANELS FOR JET PATIO DOORS +KINGDVI STD KING DUAL VENT STORM DOORS +KINGDVL STD KING DUAL VENT STORM DOORS +KINGFSC INSERTS FULL SCREEN FOR KING ONE-LITE +KINGI STD KING ONE-LITE STORM DOORS +KINGL STD KING ONE-LITE STORM DOORS +KINGSDI STD KING ONE-LITE SCREEN DOOR ONLY +KINGSDL STD KING ONE-LITE SCREEN DOOR ONLY +LINCOLN 3000DHP LINCOLN PRIMARY WOOD WINDOWS +LINCPDR PD LINCOLN FRENCH PATIO DOOR +M1200 PD M1200 PATIO STORM DOOR +M306 INSERTS M-306 SCHLEGEL GLASS INSERTS +OUTSIDE STDMISC OUTSIDE DOOR SWEEPS - ALUM + VINYL +PATIOSC SCREENS PATIO DOOR SCREENS +PDSCRTT SCREENS SPECIAL SIZE SCREENS FOR PATIO DOORS +PRPDS SCREENS SCREENS MADE FROM PLAIN PATIO SCR RAIL +PRSCR PSCREEN SCREENS FOR PRIME MADE FROM #19-88 +PRSCR11 PSCREEN SCREEN FOR PRIME MADE FROM #19-11 +PSINS INSERTS PLAIN SASH INSERTS +PWS STPW PIN-ON PICTURE WINDOWS +PWSINS INSERTS INSERTS ONLY FOR PIN-ON PICTURE WINDOW +R1150 REPLACE R-1150 VINYL AWNING WINDOWS +R1400 REPLACE SERIES 1400 VINYL SLIDING PRIMARY +R1500 REPLACE SERIES 1500 S.H. TILT VINYL PRIMARY +R1510 FPPW SERIES 1510 FIXED LITE VINYL PRIMARY +R2000 REPLACE SERIES 2000 S.H. T.B. TILT PRIMARY +R2100 REPLACE SERIES 2100 THERMAL BREAK SLIDER +R2100EV R2000SL SERIES 2100 3-PANEL T.B. SLIDER +R2200 RFPPW SERIES 2200 T.B. FIXED LITE +R300 RBSMT C-300 ALUM INSERTS FOR BASEMENT BUCKS +R3302 CASEMNT 3302 2-PANEL T.B. CASEMENT +R400 RBSMT C-400 VINYL INSERT FOR BASEMENT BUCKS +R5000 REPLACE R-5000 INSULATED ALUMINUM SLIDER +R770 REPLACE R770 D.H. TILT VINYL PRIMARY WINDOWS +R770SCR SCREENS FULL SCREEN +R780 REPLACE R780 VINYL SLIDING PRIMARY +R820 REPLACE R820 S.H. TILT VINYL PRIMARY WINDOWS +R821 REPLACE S821 SINGLE SLIDE VINYL PRIMARY +R822 FPPW S822 FIXED VINYL PRIMARY WINDOWS +R830 REPLACE R830 D.H.TILT VINYL PRIMARY WINDOWS +R832 FPPW R832 FIXED VINYL PRIMARY WINDOWS +RCKTRAP INSERTS ROCKET TRAPEZOIDS +REWIRE SCREENS REWIRED SCREENS +RNDROCK INSERTS ROUND ROCKET INSERT +ROCKET INSERTS ROCKET INSERTS +ROYAL STD COLUMBIA ROYAL STORM DOOR +RROCKET INSERTS RADIUS ROCKETS +S820 SHPT S820 S.H. TILT VINYL PRIMARY WINDOWS +S821 SHPW S821 SINGLE SLIDE VINYL PRIMARY +S822 FPPW S822 FIXED VINYL PRIMARY WINDOWS +SCRI INSERTS SCREEN INSERTS FOR ECONOMY STORM WDS +SCRI404 INSERTS SCREEN INSERTS FOR #404/450 STORM WDS +SCRI606 INSERTS SCREEN INSERTS FOR #606/650 STORM WDS +SCRI808 INSERTS SCREEN INSERTS FOR #808/850 STORM WIND +SS10I STD COBRA SS-10 DECORATOR STORM DOOR +SS10L STD COBRA SS-10 DECORATOR STORM DOOR +SS3I STD COBRA SS-3 DECORATOR STORM DOOR +SS3L STD COBRA SS-3 DECORATOR STORM DOOR +SSGLASS GLASS SINGLE STRENGTH GLASS +SSSCR INSERTS SCREEN INSERT FOR SELF STORING DOORS +TBGI INSERTS TEMPERED GLASS INSERTS FOR SS DOORS +TBR PD IMPERIAL T.B.R. REPLACEMENT PATIO DOOR +TGI INSERTS TOP GL INSERTS FOR ECONOMY ST WDS +TGI404 INSERTS TOP GLASS INSERTS FOR #404/450 ST WDS +TGI606 INSERTS TOP GLASS INSERTS FOR #606/650 ST WDS +TGIODD INSERTS TOP GLASS INSERTS FOR STORM DOORS +THOR STD THOR - STORM DOOR +TIARAI STD COLUMBIA TIARA SELF STORING STORM DOOR +TIARAL STD COLUMBIA TIARA SELF STORING STORM DOOR +TVGROOV INSERTS TEMPERED V-GROOVE ONE-LITE INSERTS +TVI STD COLUMBIA COBRA TWIN VENT STORM DOOR +TVL STD COLUMBIA COBRA TWIN VENT STORM DOOR +VKI STOCK VENTILATOR KICKPANEL FOR KING ONELITE +VKS STOCK VENTILATOR SCREEN FOR KING ONE LITE +VP3700 VENTS VENT PANELS FOR #3700 +WINDGAT VACW ALLIANCE WINDGATE CASEMENT WINDOW +XBUCKIN INSERTS TEMPERED GLASS INSERTS FOR CROSSBUCKS +ZBARS STDMISC Z-BARS FOR STORM DOORS diff --git a/information/deprecated/index.html b/information/deprecated/index.html new file mode 100644 index 0000000..be21f87 --- /dev/null +++ b/information/deprecated/index.html @@ -0,0 +1,230 @@ + + + + + + Product Details Form + + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + + +
+ + +
+ + + +
+

Possible questions for customers based on what is selected or missing

+
[list - checkbox per question]
+
+
+ + + + diff --git a/information/deprecated/index2.html b/information/deprecated/index2.html new file mode 100644 index 0000000..9adfadd --- /dev/null +++ b/information/deprecated/index2.html @@ -0,0 +1,26 @@ + + + + + + Product Finder Quiz + + + +
+
+

Product Finder

+
+ + + +
+ +
+
+ + + + diff --git a/information/deprecated/run.bat b/information/deprecated/run.bat new file mode 100644 index 0000000..dab51f4 --- /dev/null +++ b/information/deprecated/run.bat @@ -0,0 +1,31 @@ +@echo off +echo ==================================== +echo Product Finder Quiz - Flask App +echo ==================================== +echo. +echo Checking Python installation... +python --version +if errorlevel 1 ( + echo ERROR: Python is not installed or not in PATH + echo Please install Python 3.8 or higher + pause + exit /b 1 +) +echo. +echo Installing dependencies... +pip install -r requirements.txt +if errorlevel 1 ( + echo ERROR: Failed to install dependencies + pause + exit /b 1 +) +echo. +echo Starting Flask application... +echo. +echo Application will be available at: +echo http://localhost:8080/quiz +echo. +echo Press Ctrl+C to stop the server +echo. +python app.py +pause diff --git a/information/deprecated/temp b/information/deprecated/temp new file mode 100644 index 0000000..a63bbea --- /dev/null +++ b/information/deprecated/temp @@ -0,0 +1,13 @@ +https://columbiawindows.com/product-detail/cobra-aluminum-storm-doors-1100/ + +https://columbiawindows.com./wp-content/uploads/2014/10/How-to-Measure2.pdf + + + + + + +HINGE LOCATION: +Face your door opening from outside the house. If hinges are needed on the left +side, specify a left hinge door. If hinges are needed on the right, specify a right +hinge door \ No newline at end of file diff --git a/information/products-layered-example.json b/information/products-layered-example.json new file mode 100644 index 0000000..a5dec11 --- /dev/null +++ b/information/products-layered-example.json @@ -0,0 +1,106 @@ +[ + { + "id": "404", + "productCode": "404", + "category": "STORMS", + "description": "#404 FALCON STORM WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Storm Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "White", + "Bronze", + "Sandstone" + ], + "isAccessory": false, + "compatibleAccessories": [], + "image": "images/404.jpg", + "imageConfig": { + "layered": true, + "basePath": "images/products/404/", + "layers": { + "base": "base.jpg", + "door": { + "white": "door-white.png", + "black": "door-black.png", + "bronze": "door-bronze.png", + "sandstone": "door-sandstone.png" + }, + "hardware": "handle.png" + } + } + }, + { + "id": "450", + "productCode": "450", + "category": "STORMS", + "description": "#450 RAVEN STORM WINDOWS", + "discontinued": false, + "location": "Iola", + "baseType": "Window", + "subType": { + "door": null, + "window": "Storm Window" + }, + "materials": [ + "Aluminum" + ], + "colors": [ + "Black", + "Bronze" + ], + "isAccessory": false, + "compatibleAccessories": [], + "image": "images/450.jpg" + }, + { + "id": "505", + "productCode": "505", + "category": "DOORS", + "description": "#505 STORM DOOR", + "discontinued": false, + "location": "Iola", + "baseType": "Door", + "subType": { + "door": "Storm Door", + "window": null + }, + "materials": [ + "Aluminum", + "Vinyl" + ], + "colors": [ + "White", + "Black", + "Bronze", + "Tan" + ], + "isAccessory": false, + "compatibleAccessories": ["ACC-001", "ACC-002"], + "image": "images/505.jpg", + "imageConfig": { + "layered": true, + "basePath": "images/products/505/", + "layers": { + "base": "base.jpg", + "door": { + "white": "door-white.png", + "black": "door-black.png" + }, + "hardware": "handle-lever.png", + "overlay": { + "inside": "view-inside.png", + "outside": "view-outside.png" + } + } + } + } +] diff --git a/planning/ENCODING_SYSTEM.md b/planning/ENCODING_SYSTEM.md new file mode 100644 index 0000000..e719c90 --- /dev/null +++ b/planning/ENCODING_SYSTEM.md @@ -0,0 +1,239 @@ +# Product Encoding System + +## Overview + +This document describes the hex-based encoding system for creating stable, bookmarkable URLs for product configurations. The system uses bit-packing to create compact strings that encode product type, color, material, and hardware options. + +## Design Goals + +1. **Stable URLs**: Old bookmarks continue to work even as new options are added +2. **Compact**: Keep URLs under 32-64 characters +3. **Extensible**: Reserve space for future expansion +4. **Reversible**: Each encoded string can be uniquely decoded back to its components +5. **Version-aware**: Support future format changes without breaking old URLs + +## Encoding Format + +``` +Format: v-T-C-M-HHHH-HHHH-HHHH + │ │ │ │ │ │ └── Hardware 3 (optional) + │ │ │ │ │ └─────── Hardware 2 (optional) + │ │ │ │ └──────────── Hardware 1 + │ │ │ └────────────── Material (1 hex char, 0-F) + │ │ └──────────────── Color (1 hex char, 0-F) + │ └────────────────── Type (1 hex char, 0-F) + └──────────────────── Version (1 hex char, 0-F) + +Example: "0-1-4-1-0D94" +- Version: 0 (using 5-bit encoding per field) +- Type: 1 (Patio Door) +- Color: 4 (White) +- Material: 1 (Aluminum) +- Hardware 1: 0D94 (Lever, Standard, Brass) + +Max Length: ~24 characters (with 3 hardware items) +``` + +## Version 0 Encoding (Current) + +### Main Product Attributes (Single Hex Character Each) + +Each main attribute uses a single hex character (0-F = 0-15): + +#### Type Values (1 hex char) +``` +1 = Patio Door +2 = Reserved +3 = Storm Door +4 = Reserved +5 = Storm Window +6 = Reserved +7 = Primary Window +8-F = Reserved (8 slots for future door/window types) +``` + +#### Color Values (1 hex char) +``` +1 = Black +2 = Bronze +3 = Sandstone +4 = White +5 = Tan +6 = Mill +7-F = Reserved (9 slots for future colors) +``` + +#### Material Values (1 hex char) +``` +1 = Aluminum +2 = Vinyl +3-F = Reserved (13 slots for future materials) +``` + +### Hardware Encoding (4 Hex Characters = FFFF) + +Each hardware item uses **4 hex characters** (16 bits) split into fields using **5 bits per field**: + +``` +16 bits total: +- Bits 10-14: Type (5 bits, 0-31 values) +- Bits 5-9: Style (5 bits, 0-31 values) +- Bits 0-4: Color (5 bits, 0-31 values) +- Bit 15: Reserved (1 bit) +``` + +#### Hardware Type Values (5 bits = 0-31) +``` +0 = None/Not Set +1 = Lever +2 = Pull +3 = Pull Handle +4 = Deadbolt +5 = Hinge +6-31 = Reserved (26 slots) +``` + +#### Hardware Style Values (5 bits = 0-31) +``` +0 = None/Not Set +1 = Standard +2 = Push +3 = Alternative +4 = Contemporary +5 = Traditional +6-31 = Reserved (26 slots) +``` + +#### Hardware Color Values (5 bits = 0-31) +``` +0 = None/Not Set +1 = Brass +2 = White +3 = Black +4 = Satin +5 = Nickel +6 = Bronze +7-31 = Reserved (25 slots) +``` + +## Bit Math Examples + +### Encoding Hardware + +``` +Example: Lever, Standard, Brass +- Type: 1 (Lever) +- Style: 1 (Standard) +- Color: 1 (Brass) + +Binary calculation: +Type (1) = 00001 (bits 10-14) +Style (1) = 00001 (bits 5-9) +Color (1) = 00001 (bits 0-4) + +Combined: 0000010000100001 = 0x0421 +Hex: "0421" +``` + +``` +Example: Pull Handle, Alternative, Satin +- Type: 3 (Pull Handle) +- Style: 3 (Alternative) +- Color: 4 (Satin) + +Binary calculation: +Type (3) = 00011 (bits 10-14) +Style (3) = 00011 (bits 5-9) +Color (4) = 00100 (bits 0-4) + +Combined: 0000110001100100 = 0x0C64 +Hex: "0C64" +``` + +### Decoding Hardware + +``` +Given hex: "0D94" +Binary: 0000110110010100 + +Extract fields: +Type = bits 10-14 = 00011 = 3 (Pull Handle) +Style = bits 5-9 = 01100 = 12 +Color = bits 0-4 = 10100 = 20 +``` + +## URL Examples + +### Simple Products (No Hardware) + +``` +"0-1-4-1" = Patio Door, White, Aluminum +"0-3-4-1" = Storm Door, White, Aluminum +"0-7-1-1" = Primary Window, Black, Aluminum +"0-5-2-2" = Storm Window, Bronze, Vinyl +``` + +### Products with Hardware + +``` +"0-3-4-1-0421" = Storm Door, White, Aluminum, Lever-Standard-Brass +"0-3-1-1-0421-0C64" = Storm Door, Black, Aluminum, + Hardware1: Lever-Standard-Brass, + Hardware2: Pull Handle-Alternative-Satin +``` + +## Expansion Strategy + +### Adding New Options (Compatible) + +When adding new options within existing capacity (0-15 for main attributes, 0-31 for hardware fields): + +``` +Old system: Color 1-6 defined, 7-15 reserved +New system: Add Color 7 = Charcoal + +Old URLs still work: "0-1-4-1" still means Patio Door, White, Aluminum +New URLs use new values: "0-1-7-1" means Patio Door, Charcoal, Aluminum +``` + +### Version 1 (Future Expansion) + +If we exceed 16 main attribute values or 32 hardware field values: + +``` +Version 1: Use 6 bits per hardware field (64 values each) +Format: "1-TT-CC-MM-HHHHH-HHHHH" + +Changes: +- Main attributes expand to 2 hex chars each (256 values) +- Hardware expands to 5 hex chars each (18 bits = 6 bits per field) +``` + +## Compatibility Rules + +1. **Never reuse reserved values** until a new version is released +2. **Never change existing value meanings** (e.g., don't make "1" mean something else) +3. **Always decode based on version character** +4. **Maintain lookup tables** for each version + +## Implementation Notes + +- Use bit-shifting operators (`<<`, `>>`) for performance +- Use bitwise AND (`&`) with masks to extract fields +- Pad hex strings with leading zeros to fixed width +- Validate decoded values against valid ranges +- Return error for unknown version codes + +## Benefits + +āœ… **Stable**: Values 0-15 remain the same even if we expand to 0-31 +āœ… **Compact**: 13-24 characters for most products +āœ… **Readable**: Hex is human-debuggable +āœ… **Efficient**: Single parse operation, no string splitting +āœ… **Extensible**: Version prefix allows future format changes +āœ… **Reliable**: Bit operations are deterministic and reversible + +--- + +**Last Updated**: March 26, 2026 +**Current Version**: 0 (5-bit hardware encoding) diff --git a/planning/ENCODING_TABLE_GENERATION.md b/planning/ENCODING_TABLE_GENERATION.md new file mode 100644 index 0000000..64a5a9d --- /dev/null +++ b/planning/ENCODING_TABLE_GENERATION.md @@ -0,0 +1,505 @@ +# Encoding Table Generation System + +## Problem Statement + +Currently, encoding lookup tables require manual maintenance of both forward and reverse mappings: + +```javascript +// Must maintain both of these manually +const PRODUCT_COLOR = { + 'BLACK': 1, + 'BRONZE': 2, + // ... add new color here +}; + +const PRODUCT_COLOR_REVERSE = { + 1: 'BLACK', + 2: 'BRONZE', + // ... and remember to add it here too! +}; +``` + +**Issues:** +- āŒ Prone to human error (forgetting reverse mapping) +- āŒ Risk of mismatches between forward/reverse +- āŒ No validation for duplicate values +- āŒ No documentation of available slots +- āŒ Difficult for non-technical users to add values + +## Proposed Solution: Python-Generated Tables + +### Architecture + +``` +User edits simple source file + ↓ +Python validation script + ↓ +Generate JavaScript + Python constants + ↓ +Auto-served by Flask (or manual build step) +``` + +### File Structure + +``` +app/ + data/ + encoding_values.json (or .yaml) ← USER EDITS THIS + js/ + encoding_tables.js ← AUTO-GENERATED + generate_encoding_tables.py ← GENERATOR SCRIPT +``` + +## Source File Format + +### Option 1: JSON with Comments Support + +**`app/data/encoding_values.json`** +```json +{ + "metadata": { + "version": 0, + "description": "Product encoding value definitions" + }, + "tables": { + "product_type": { + "max_value": 15, + "bit_size": 4, + "values": [ + {"name": "PATIO_DOOR", "value": 1, "display": "Patio Door"}, + {"name": "STORM_DOOR", "value": 3, "display": "Storm Door"}, + {"name": "STORM_WINDOW", "value": 5, "display": "Storm Window"}, + {"name": "PRIMARY_WINDOW", "value": 7, "display": "Primary Window"} + ] + }, + "product_color": { + "max_value": 15, + "bit_size": 4, + "values": [ + {"name": "BLACK", "value": 1, "display": "Black"}, + {"name": "BRONZE", "value": 2, "display": "Bronze"}, + {"name": "SANDSTONE", "value": 3, "display": "Sandstone"}, + {"name": "WHITE", "value": 4, "display": "White"}, + {"name": "TAN", "value": 5, "display": "Tan"}, + {"name": "MILL", "value": 6, "display": "Mill"} + ] + }, + "product_material": { + "max_value": 15, + "bit_size": 4, + "values": [ + {"name": "ALUMINUM", "value": 1, "display": "Aluminum"}, + {"name": "VINYL", "value": 2, "display": "Vinyl"} + ] + }, + "hardware_type": { + "max_value": 31, + "bit_size": 5, + "values": [ + {"name": "NONE", "value": 0, "display": "None"}, + {"name": "LEVER", "value": 1, "display": "Lever"}, + {"name": "PULL", "value": 2, "display": "Pull"}, + {"name": "PULL_HANDLE", "value": 3, "display": "Pull Handle"}, + {"name": "DEADBOLT", "value": 4, "display": "Deadbolt"}, + {"name": "HINGE", "value": 5, "display": "Hinge"} + ] + }, + "hardware_style": { + "max_value": 31, + "bit_size": 5, + "values": [ + {"name": "NONE", "value": 0, "display": "None"}, + {"name": "STANDARD", "value": 1, "display": "Standard"}, + {"name": "PUSH", "value": 2, "display": "Push"}, + {"name": "ALTERNATIVE", "value": 3, "display": "Alternative"}, + {"name": "CONTEMPORARY", "value": 4, "display": "Contemporary"}, + {"name": "TRADITIONAL", "value": 5, "display": "Traditional"} + ] + }, + "hardware_color": { + "max_value": 31, + "bit_size": 5, + "values": [ + {"name": "NONE", "value": 0, "display": "None"}, + {"name": "BRASS", "value": 1, "display": "Brass"}, + {"name": "WHITE", "value": 2, "display": "White"}, + {"name": "BLACK", "value": 3, "display": "Black"}, + {"name": "SATIN", "value": 4, "display": "Satin"}, + {"name": "NICKEL", "value": 5, "display": "Nickel"}, + {"name": "BRONZE", "value": 6, "display": "Bronze"} + ] + } + } +} +``` + +### Option 2: YAML (Better for Comments) + +**`app/data/encoding_values.yaml`** +```yaml +version: 0 +description: Product encoding value definitions + +tables: + product_type: + max_value: 15 + bit_size: 4 + values: + - name: PATIO_DOOR + value: 1 + display: Patio Door + - name: STORM_DOOR + value: 3 + display: Storm Door + # Reserved: 2, 4, 6, 8-15 + + product_color: + max_value: 15 + bit_size: 4 + values: + - name: BLACK + value: 1 + display: Black + - name: BRONZE + value: 2 + display: Bronze + # ... etc + # Reserved: 7-15 (9 slots available) +``` + +## User Workflow + +### Adding a New Color + +**Before (Manual):** +1. Open `encoding_helper.js` +2. Find `PRODUCT_COLOR` object +3. Add new entry with unused value +4. Find `PRODUCT_COLOR_REVERSE` object +5. Add matching reverse entry +6. Hope you didn't make a mistake + +**After (Generated):** +1. Open `encoding_values.json` +2. Add one line: `{"name": "CHARCOAL", "value": 7, "display": "Charcoal"}` +3. Run `python generate_encoding_tables.py` (or auto-regenerated by Flask) +4. Done! JavaScript and Python constants updated + +## Generator Script + +**`app/generate_encoding_tables.py`** + +```python +#!/usr/bin/env python3 +""" +Generate JavaScript and Python encoding tables from source JSON/YAML. + +Usage: + python generate_encoding_tables.py + + Or add to Flask app for auto-generation on file change. +""" + +import json +import os +from datetime import datetime +from pathlib import Path + +def load_source_file(filepath): + """Load encoding values from JSON or YAML.""" + with open(filepath, 'r') as f: + if filepath.endswith('.yaml') or filepath.endswith('.yml'): + import yaml + return yaml.safe_load(f) + else: + return json.load(f) + +def validate_table(table_name, table_data): + """Validate a single table for errors.""" + errors = [] + max_value = table_data['max_value'] + values = table_data['values'] + + used_values = set() + used_names = set() + + for item in values: + name = item['name'] + value = item['value'] + + # Check for duplicate values + if value in used_values: + errors.append(f"{table_name}: Duplicate value {value}") + used_values.add(value) + + # Check for duplicate names + if name in used_names: + errors.append(f"{table_name}: Duplicate name '{name}'") + used_names.add(name) + + # Check value range + if value < 0 or value > max_value: + errors.append(f"{table_name}: Value {value} out of range (0-{max_value})") + + # Validate naming convention (uppercase with underscores) + if not name.isupper() or not name.replace('_', '').isalpha(): + errors.append(f"{table_name}: Invalid name format '{name}' (use UPPERCASE_WITH_UNDERSCORES)") + + # Report available slots + available = sorted(set(range(max_value + 1)) - used_values) + info = f"{table_name}: {len(used_values)}/{max_value + 1} slots used, {len(available)} available" + if available and len(available) <= 10: + info += f" {available}" + + return errors, info + +def generate_javascript(data, output_path): + """Generate JavaScript constants file.""" + lines = [ + "// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY", + "// Generated from: app/data/encoding_values.json", + f"// Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + "//", + "// To add new values:", + "// 1. Edit app/data/encoding_values.json", + "// 2. Run: python app/generate_encoding_tables.py", + "// 3. Restart Flask server (or it auto-reloads)", + "", + "// ============================================================================", + "// ENCODING CONSTANTS", + "// ============================================================================", + "", + f"const ENCODING_VERSION = {data['metadata']['version']};", + "" + ] + + for table_name, table_data in data['tables'].items(): + const_name = table_name.upper() + + # Generate forward mapping + lines.append(f"// {const_name} (max: {table_data['max_value']}, bits: {table_data['bit_size']})") + lines.append(f"const {const_name} = {{") + for item in table_data['values']: + lines.append(f" '{item['name']}': {item['value']},") + lines.append("};") + lines.append("") + + # Generate reverse mapping + lines.append(f"const {const_name}_REVERSE = {{") + for item in table_data['values']: + lines.append(f" {item['value']}: '{item['name']}',") + lines.append("};") + lines.append("") + + # Generate display mapping + lines.append(f"const {const_name}_DISPLAY = {{") + for item in table_data['values']: + lines.append(f" '{item['name']}': '{item['display']}',") + lines.append("};") + lines.append("") + + # Write file + with open(output_path, 'w') as f: + f.write('\n'.join(lines)) + + print(f"āœ“ Generated: {output_path}") + +def generate_python(data, output_path): + """Generate Python constants file (optional).""" + lines = [ + '"""', + "AUTO-GENERATED FILE - DO NOT EDIT MANUALLY", + "Generated from: app/data/encoding_values.json", + f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + '"""', + "", + f"ENCODING_VERSION = {data['metadata']['version']}", + "" + ] + + for table_name, table_data in data['tables'].items(): + const_name = table_name.upper() + + lines.append(f"# {const_name} (max: {table_data['max_value']}, bits: {table_data['bit_size']})") + lines.append(f"{const_name} = {{") + for item in table_data['values']: + lines.append(f" '{item['name']}': {item['value']},") + lines.append("}") + lines.append("") + + lines.append(f"{const_name}_REVERSE = {{") + for item in table_data['values']: + lines.append(f" {item['value']}: '{item['name']}',") + lines.append("}") + lines.append("") + + with open(output_path, 'w') as f: + f.write('\n'.join(lines)) + + print(f"āœ“ Generated: {output_path}") + +def generate_documentation(data, output_path): + """Generate markdown documentation.""" + lines = [ + "# Encoding Values Reference", + "", + f"**Version:** {data['metadata']['version']} ", + f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ", + "", + "## Overview", + "", + "This document lists all currently defined encoding values.", + "", + "## Quick Reference", + "" + ] + + for table_name, table_data in data['tables'].items(): + lines.append(f"### {table_name.replace('_', ' ').title()}") + lines.append("") + lines.append(f"**Capacity:** {len(table_data['values'])}/{table_data['max_value'] + 1} slots used ") + lines.append(f"**Bit Size:** {table_data['bit_size']} bits ") + lines.append("") + lines.append("| Value | Name | Display |") + lines.append("|-------|------|---------|") + + for item in sorted(table_data['values'], key=lambda x: x['value']): + lines.append(f"| {item['value']} | `{item['name']}` | {item['display']} |") + + lines.append("") + + with open(output_path, 'w') as f: + f.write('\n'.join(lines)) + + print(f"āœ“ Generated: {output_path}") + +def main(): + """Main generation function.""" + base_dir = Path(__file__).parent + source_file = base_dir / 'data' / 'encoding_values.json' + + # Load source data + print(f"Loading: {source_file}") + data = load_source_file(source_file) + + # Validate all tables + print("\nValidating tables...") + all_errors = [] + for table_name, table_data in data['tables'].items(): + errors, info = validate_table(table_name, table_data) + print(f" {info}") + all_errors.extend(errors) + + if all_errors: + print("\nāŒ VALIDATION ERRORS:") + for error in all_errors: + print(f" - {error}") + return 1 + + print("\nāœ“ All tables valid") + + # Generate files + print("\nGenerating files...") + generate_javascript(data, base_dir / 'js' / 'encoding_tables.js') + generate_python(data, base_dir / 'encoding_tables_constants.py') + generate_documentation(data, base_dir.parent / 'planning' / 'ENCODING_VALUES_REFERENCE.md') + + print("\nāœ“ Generation complete!") + return 0 + +if __name__ == '__main__': + exit(main()) +``` + +## Flask Integration (Auto-Generation) + +Add to `app.py`: + +```python +import os +from datetime import datetime + +@app.before_request +def check_encoding_tables(): + """Auto-regenerate encoding tables if source has changed.""" + source_file = 'data/encoding_values.json' + output_file = 'js/encoding_tables.js' + + # Check if regeneration needed + if not os.path.exists(output_file): + print("Encoding tables not found, generating...") + os.system('python generate_encoding_tables.py') + else: + source_mtime = os.path.getmtime(source_file) + output_mtime = os.path.getmtime(output_file) + + if source_mtime > output_mtime: + print("Encoding values changed, regenerating tables...") + os.system('python generate_encoding_tables.py') +``` + +## Benefits + +### For Non-Technical Users +- āœ… Edit simple JSON file (no JavaScript knowledge needed) +- āœ… Clear structure with examples +- āœ… Automatic validation catches errors +- āœ… Can't create mismatches between forward/reverse + +### For Developers +- āœ… Single source of truth +- āœ… Auto-generated documentation +- āœ… Validation prevents bugs +- āœ… Easy to add new tables +- āœ… Can generate for multiple targets (JS, Python, docs) + +### For Maintenance +- āœ… Version control friendly (one file to track) +- āœ… Easy to review changes (just JSON diff) +- āœ… Reports available slots automatically +- āœ… Enforces naming conventions + +## Migration Path + +### Phase 1: Create Source File +1. Extract existing values to `encoding_values.json` +2. Test generator script +3. Verify output matches current `encoding_helper.js` + +### Phase 2: Integrate Generator +1. Add generator script to project +2. Update build process / Flask app +3. Test auto-generation + +### Phase 3: Switch to Generated Tables +1. Update `encoding_helper.js` to import generated file +2. Remove manual mappings +3. Update documentation + +### Phase 4: User Documentation +1. Create guide for adding new values +2. Document validation rules +3. Add examples + +## Future Enhancements + +- Web UI for editing values (no need to edit JSON directly) +- Export to CSV for client review +- Import from Excel/CSV +- Visual capacity indicators +- Conflict detection across tables +- Automatic value assignment (find next available slot) + +## Related Files + +- [ENCODING_SYSTEM.md](ENCODING_SYSTEM.md) - Technical specification +- `app/js/encoding_helper.js` - Current manual implementation +- `app/js/encoding_tables.js` - Future generated file + +--- + +**Status:** Planning +**Priority:** Medium +**Effort:** ~4-6 hours implementation + testing +**Dependencies:** None diff --git a/planning/VISUAL_NAVIGATION_EDITOR.md b/planning/VISUAL_NAVIGATION_EDITOR.md new file mode 100644 index 0000000..407c681 --- /dev/null +++ b/planning/VISUAL_NAVIGATION_EDITOR.md @@ -0,0 +1,2529 @@ +# Visual Navigation Editor for navigation.json + +## Concept Overview +A drag-and-drop visual interface for creating and editing the navigation flow, similar to Talend's workflow editor. This would replace manual JSON editing with an intuitive node-based editor. + +## Similar Systems & Industry Parallels + +This visual navigation editor concept follows proven patterns from industrial automation and visual programming tools: + +### **Node-RED** (Industrial IoT & Automation) +- Flow-based programming for IoT devices and industrial automation +- Drag-and-drop nodes with input/output connections +- **Sub-flows**: Reusable flow components that can be nested and shared +- Used in manufacturing, building automation, industrial control systems +- **Key Feature**: Double-click sub-flow to drill down and edit internal logic + +### **LabVIEW** (National Instruments - Industrial Control) +- Visual programming for measurement and control systems +- **SubVIs**: Reusable virtual instruments (similar to functions/methods) +- Used in aerospace, automotive testing, manufacturing quality control +- Hierarchical design: Complex systems broken into manageable sub-components +- **Key Feature**: Icon view shows high-level, double-click to edit internal diagram + +### **PLC Ladder Logic** (Industrial Machine Control) +- Visual programming for Programmable Logic Controllers +- **Function Blocks**: Reusable logic blocks for common operations +- Used in factory automation, assembly lines, industrial machinery +- Modular design allows sharing common patterns across machines + +### **Unreal Engine Blueprints** (Game Development) +- Visual scripting for game logic +- **Blueprint Functions/Macros**: Reusable logic components +- **Collapse to Function**: Select nodes, create reusable component +- Used by game developers worldwide for complex interactive systems + +### **Power Automate / Microsoft Flow** (Business Process Automation) +- No-code workflow automation +- Visual flow designer with connectors and conditions +- Used for business process automation in enterprises + +### **Why This Pattern Works** +- **Visual Clarity**: See the entire flow at a glance +- **Complexity Management**: Nest/collapse to manage large systems +- **Reusability**: Build once, use everywhere +- **Collaboration**: Team members can understand and contribute +- **Lower Barrier**: Non-programmers can build complex logic + +Your navigation editor would bring these industrial-strength patterns to product configuration workflows! + +--- + +## šŸ—ļø Foundational Architecture: Component-Based Rendering System + +### Overview + +The navigation system will use a **component-based architecture** where each question node contains an ordered array of components that render sequentially from top to bottom. This replaces the current type-based system (`inputType: "button"` vs `inputType: "form"`) with a more flexible, composable approach. + +### Key Principle + +**Components set variables, navigation controls flow.** + +- **Toggle/Field components**: Set variables in the application state +- **Navigation components**: Control which page appears next +- **Notes/Separator components**: Provide information and visual structure + +Components can be combined in any order, allowing for maximum flexibility without mixing concerns (e.g., navigation buttons remain separate from data input controls). + +### Current System vs. Component-Based System + +#### Current System (Limited) +```json +{ + "q-door-type": { + "type": "question", + "inputType": "button", // ← Only navigation OR only form + "title": "What type of door?", + "answers": [...] + }, + "q-dimensions": { + "type": "question", + "inputType": "form", // ← Can't mix with navigation + "measurementType": {...}, + "fields": [...] + } +} +``` + +**Limitations:** +- `inputType: "button"` = Only navigation buttons +- `inputType: "form"` = Only input fields (no navigation buttons) +- Can't have multiple toggle groups +- Navigation always at bottom +- Hard to add new input types + +#### Component-Based System (Flexible) +```json +{ + "q-configure-door": { + "type": "question", + "title": "Configure Your Door", + "subtitle": "Set your preferences and continue", + "components": [ + { + "type": "toggle", + "variable": "includeHardware", + "label": "Include Hardware?", + "options": [ + { "value": "yes", "label": "Yes", "default": true }, + { "value": "no", "label": "No" } + ] + }, + { + "type": "fields", + "label": "Installation Details", + "fields": [ + { "name": "installDate", "label": "Preferred Installation Date", "type": "date" }, + { "name": "notes", "label": "Special Instructions", "type": "text", "optional": true } + ] + }, + { + "type": "toggle", + "variable": "measurementType", + "label": "Measurement Type", + "options": [ + { "value": "opening-size", "label": "Opening Size", "icon": "šŸ“", "default": true }, + { "value": "tip-to-tip", "label": "Tip-to-tip", "icon": "šŸ“" } + ] + }, + { + "type": "fields", + "fields": [ + { "name": "width", "label": "Width (inches)", "type": "number", "required": true }, + { "name": "height", "label": "Height (inches)", "type": "number", "required": true } + ] + }, + { + "type": "navigation", + "answers": [ + { "caption": "Continue", "next": "results", "primary": true }, + { "caption": "Skip Optional Settings", "next": "q-accessories" } + ] + }, + { + "type": "notes", + "content": "

Tip: For accurate measurements, refer to our measuring guide.

" + } + ] + } +} +``` + +**Advantages:** +- āœ… Mix any components in any order +- āœ… Multiple toggle groups in same question +- āœ… Multiple field groups in same question +- āœ… Navigation can be anywhere (top, middle, bottom) +- āœ… Easy to add new component types +- āœ… Clean separation of concerns +- āœ… Editor-friendly (drag components to reorder) + +### Component Type Reference + +#### 1. **Toggle Component** +Sets a variable by presenting mutually exclusive options (radio button style). + +```json +{ + "type": "toggle", + "variable": "rushOrder", + "label": "Rush Order?", + "options": [ + { "value": "yes", "label": "Rush (3-5 days)", "icon": "⚔", "default": false }, + { "value": "no", "label": "Standard (2-3 weeks)", "icon": "šŸ“¦", "default": true } + ] +} +``` + +**Properties:** +- `variable` (string, required): Variable name to store selection +- `label` (string, optional): Section heading +- `options` (array, required): Available choices + - `value`: Stored value + - `label`: Display text + - `icon`: Optional emoji/icon + - `default`: Whether this is the default selection + - `disabled`: Whether this option is disabled + +#### 2. **Fields Component** +Group of input fields for collecting user data. + +```json +{ + "type": "fields", + "label": "Product Dimensions", + "fields": [ + { + "name": "width", + "label": "Width (inches)", + "type": "number", + "required": true, + "min": 12, + "max": 120, + "placeholder": "26" + }, + { + "name": "height", + "label": "Height (inches)", + "type": "number", + "required": true, + "placeholder": "81" + } + ] +} +``` + +**Properties:** +- `label` (string, optional): Group heading +- `fields` (array, required): Input fields + - `name`: Variable name + - `label`: Field label + - `type`: Input type (text, number, date, email, tel, textarea) + - `required`: Whether field is required + - `placeholder`: Placeholder text + - `min`, `max`: Constraints for number/date fields + - `pattern`: Regex validation pattern + +#### 3. **Navigation Component** +Navigation buttons to control flow to next question/page. + +```json +{ + "type": "navigation", + "answers": [ + { + "caption": "Continue", + "next": "results", + "primary": true, + "filter": { "configComplete": true } + }, + { + "caption": "Add Accessories", + "next": "q-accessories" + }, + { + "caption": "Start Over", + "next": "start", + "style": "secondary" + } + ] +} +``` + +**Properties:** +- `answers` (array, required): Navigation buttons + - `caption`: Button text (can include HTML) + - `image`: Optional icon/emoji + - `next`: Target question ID + - `primary`: Style as primary action button + - `style`: Button style (primary, secondary, danger) + - `filter`: Filter object to apply on click + - `dimensions`: Dimension object to set on click + +#### 4. **Notes Component** +Static informational content (tips, instructions, links). + +```json +{ + "type": "notes", + "content": "

How to Measure:

  • Measure the rough opening
  • Round down to nearest inch
", + "style": "info" +} +``` + +**Properties:** +- `content` (string, required): HTML content +- `style` (string, optional): Visual style (info, warning, tip, default) + +#### 5. **Separator Component** (Optional) +Visual divider between sections. + +```json +{ + "type": "separator", + "style": "line" +} +``` + +**Properties:** +- `style`: Separator style (line, space, none) + +### Rendering Order Example + +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Configure Your Door │ ← Title/Subtitle +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ Include Hardware? │ +│ ā— Yes ā—‹ No │ ← Toggle Component #1 +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ Installation Details │ +│ [Preferred Installation Date: ____] │ +│ [Special Instructions: _________] │ ← Fields Component #1 +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ Measurement Type │ +│ ā— Opening Size ā—‹ Tip-to-tip │ ← Toggle Component #2 +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ Width (inches): [____] │ +│ Height (inches): [____] │ ← Fields Component #2 +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ [Continue] [Add Accessories] │ ← Navigation Component +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ šŸ’” Tip: For accurate measurements... │ ← Notes Component +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Rendering Logic Implementation + +```javascript +function renderQuestion(questionData) { + // Render title/subtitle + renderHeader(questionData.title, questionData.subtitle); + + // Iterate through components array + questionData.components.forEach((component, index) => { + const container = document.createElement('div'); + container.className = `component component-${component.type}`; + container.dataset.componentIndex = index; + + switch(component.type) { + case 'toggle': + renderToggleComponent(container, component); + break; + case 'fields': + renderFieldsComponent(container, component); + break; + case 'navigation': + renderNavigationComponent(container, component); + break; + case 'notes': + renderNotesComponent(container, component); + break; + case 'separator': + renderSeparatorComponent(container, component); + break; + default: + console.warn(`Unknown component type: ${component.type}`); + } + + document.getElementById('question-container').appendChild(container); + }); +} + +function handleFormSubmit(questionData) { + const variables = {}; + + // Collect all variable values from toggle and field components + questionData.components.forEach(component => { + if (component.type === 'toggle') { + const selected = document.querySelector(`input[name="${component.variable}"]:checked`); + variables[component.variable] = selected ? selected.value : null; + } + if (component.type === 'fields') { + component.fields.forEach(field => { + const input = document.querySelector(`[name="${field.name}"]`); + variables[field.name] = input ? input.value : null; + }); + } + }); + + // Validate all required fields + if (!validateComponents(questionData.components)) { + return false; + } + + // Save variables to userAnswers + Object.assign(userAnswers, variables); + + // Navigation is handled by navigation component button clicks + return true; +} +``` + +### Migration Path from Current System + +To maintain backwards compatibility during transition: + +1. **Phase 1: Dual Support** - Support both old and new formats +2. **Phase 2: Auto-conversion** - Auto-convert old format to components on load +3. **Phase 3: Deprecation** - Remove old format support + +```javascript +function normalizeQuestion(questionData) { + // If already using components, return as-is + if (questionData.components) { + return questionData; + } + + // Auto-convert old format to components + const components = []; + + if (questionData.inputType === 'button') { + // Convert to navigation component + components.push({ + type: 'navigation', + answers: questionData.answers + }); + } + + if (questionData.inputType === 'form') { + // Convert measurementType to toggle component + if (questionData.measurementType) { + components.push({ + type: 'toggle', + variable: 'measurementType', + options: questionData.measurementType.options + }); + } + + // Convert fields to fields component + if (questionData.fields) { + components.push({ + type: 'fields', + fields: questionData.fields + }); + } + + // Add navigation if next exists + if (questionData.next) { + components.push({ + type: 'navigation', + answers: [ + { caption: 'Continue', next: questionData.next } + ] + }); + } + } + + // Add notes component if notes exist + if (questionData.notes) { + components.push({ + type: 'notes', + content: questionData.notes + }); + } + + return { + ...questionData, + components + }; +} +``` + +### Benefits for Visual Editor + +This component-based architecture makes the visual editor **dramatically simpler** to implement: + +#### **Component Palette** +Drag components from palette onto canvas: +- šŸŽšļø Toggle +- šŸ“ Fields +- šŸ”— Navigation +- šŸ“„ Notes +- āž– Separator + +#### **Component List View** +Within each tile, show reorderable list of components: +``` +šŸ“‹ Configure Door + šŸŽšļø Toggle: Include Hardware? + šŸ“ Fields: Installation Details (2 fields) + šŸŽšļø Toggle: Measurement Type + šŸ“ Fields: Dimensions (2 fields) + šŸ”— Navigation: 2 buttons + šŸ“„ Notes: Measuring tip + [+ Add Component] +``` + +#### **Drag to Reorder** +Users can drag components up/down within the list to change render order. + +#### **Click to Configure** +Clicking a component opens the config panel with type-specific options. + +#### **Editor UI Mockup** +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Component Palette │ +│ ā”Œā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā” │ +│ ā”‚šŸŽšļø │ ā”‚šŸ“ │ ā”‚šŸ”— │ ā”‚šŸ“„ │ ā”‚āž– │ │ +│ ā””ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”˜ │ +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ Selected Tile: q-configure-door │ +│ │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ šŸŽšļø Toggle: Include Hardware? [↑][↓] │ │ +│ │ šŸ“ Fields: Installation Details [↑][↓] │ │ +│ │ šŸŽšļø Toggle: Measurement Type [↑][↓] │ │ +│ │ šŸ“ Fields: Dimensions [↑][↓] │ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ +│ │ šŸ”— Navigation: 2 buttons [↑][↓] │ │ Config │ │ +│ │ šŸ“„ Notes: Measuring tip [↑][↓] │ │ Panel │ │ +│ │ │ │ │ │ +│ │ [+ Add Component ā–¼] │ │ │ │ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Implementation Considerations + +**Validation:** +- Ensure at least one navigation component exists in reachable nodes +- Validate variable naming (no conflicts) +- Ensure required fields are marked + +**State Management:** +- All variables collected before navigation +- Variables persist across question navigation +- Can reference previous answers in conditional logic + +**Extensibility:** +- Easy to add new component types (checkbox group, radio group, slider, datepicker, file upload, etc.) +- Plugin architecture for custom components +- Community-contributed component library + +**Editor Features:** +- Duplicate component +- Copy/paste components between tiles +- Component templates/presets +- Import component from library + +--- + +## UI Layout + +### Two-Panel Design + +#### Panel 1: Canvas/Drop Area (Left/Main Panel) +- **Drag-and-drop workspace** where navigation nodes (tiles) can be moved around +- **Tiles/Nodes** represent each question/step in the navigation +- **Connection arrows** show the flow between steps +- **Colored circles** (connection points) on tiles indicate multiple possible outputs + - Number of circles determined by configuration (buttons, alternatives, etc.) + - Circles appear when tile is selected + - Different colors for different types of connections +- **Grid/snap system** for clean alignment +- **Zoom and pan** controls for large navigation flows +- **Minimap** for overview of complex flows + +#### Panel 2: Configuration Panel (Right/Sidebar) +- **Tile Properties** editor (activated when a tile is selected) +- **Dynamic form fields** based on question type: + - Question ID, title, subtitle + - Question type selector (buttons, dimensions, multi-select, etc.) + - Type-specific options (button labels, icons, validation, etc.) +- **Output/Connection Groups** section: + - Group buttons together that share the same output path + - Add/remove connection groups + - Each group maps to a colored circle on the tile + - Default output selection (for ungrouped actions) +- **Default Values** section: + - List of variable assignments + - Each row: `[Variable Dropdown] : [Value Field] [Remove Button]` + - `[Add Another]` button to create new default assignments +- **Validation** section for input requirements +- **Conditional Logic** editor for complex routing + +## Features + +### Node/Tile Management +- **Visual representation** of each navigation step + - Title displayed on tile + - Icon/badge showing question type + - Color coding by category +- **Multiple outputs** via colored circles: + - Small circles appear on tile edges when selected + - Each circle represents a possible path forward + - Example: Door size question has 2 circles (predefined sizes, custom dimensions) +- **Drag and drop** positioning +- **Auto-layout** option to organize nodes cleanly +- **Duplicate node** feature for similar questions +- **Delete with confirmation** and automatic connection cleanup + +### Connection System + +#### Option A: Click-and-Drag (Visual) +- Click a circle on source tile +- Drag to create arrow/line +- Drop on target tile to connect +- Arrow shows direction of flow +- Hover shows connection metadata (condition, button labels) + +#### Option B: Dropdown Selection (Form-based) +- Select a connection group in config panel +- Dropdown shows all available destination tiles +- Select target from list +- Visual arrow updates automatically + +#### Option C: Hybrid Approach (Recommended) +- Both methods available +- Click-drag for quick visual connections +- Dropdown for precise selection or when tiles are far apart +- Arrows can be clicked to edit connection details + +### Button/Output Grouping +Buttons within a tile can be grouped to share the same output: + +**Example 1: Door Size Question** +- Group 1: "All predefined size buttons" → Circle 1 (blue) → Next standard question +- Group 2: "Custom dimensions button" → Circle 2 (orange) → Alternative dimension entry +- Default: Any ungrouped actions → Circle 3 (gray) → Fallback route + +**Example 2: Simple Yes/No Question** +- Group 1: "Yes button" → Circle 1 → Path A +- Group 2: "No button" → Circle 2 → Path B + +**Configuration UI:** +``` +Output Groups: +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Group 1: Predefined Sizes │ +│ Buttons: [32x80, 36x80, other...] │ +│ → Connects to: [q-hinges ā–¼] │ +│ Circle color: [šŸ”µ Blue] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Group 2: Custom Entry │ +│ Buttons: [Enter Custom] │ +│ → Connects to: [q-alt-path ā–¼] │ +│ Circle color: [🟠 Orange] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Default Output: [q-next-step ā–¼] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +### Default Values System +Users need to set default variable values that apply when reaching certain nodes. + +**Configuration UI:** +``` +Default Values to Set: +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ [doorType ā–¼] : [storm door ] [āœ–] │ +│ [measurementSystem ā–¼]: [imperial ] [āœ–] │ +│ [region ā–¼] : [north-america ] [āœ–] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +[+ Add Another Default Value] +``` + +**Features:** +- Variable dropdown populated from: + - Previously used variables across all navigation nodes + - Manual entry option to add new variables + - Suggesting common variables based on question type +- Value field accepts: + - Static values + - References to other variables (e.g., `${previousAnswer}`) + - Expressions for calculations +- Remove button to delete a default value assignment +- Add button to create additional assignments + +## Technical Implementation + +### Technology Stack Options + +#### Option 1: Web-Based Standalone Tool +**Frontend:** +- **React** or **Vue.js** for UI framework +- **React Flow** or **Vue Flow** for node-based canvas + - Built-in drag-drop, zoom, pan + - Customizable nodes and connections + - Good performance with large graphs +- **Tailwind CSS** or **Material-UI** for styling +- **Monaco Editor** for any direct JSON editing fallback + +**Backend:** +- **Python Flask** (existing stack) with endpoints: + - `GET /api/editor/navigation` - Load current navigation.json + - `POST /api/editor/navigation` - Save edited navigation + - `GET /api/editor/validate` - Validate navigation structure + - `GET /api/editor/variables` - Get list of known variables +- File system operations to read/write navigation.json +- Validation logic to ensure navigation structure is valid + +**Pros:** +- Can be integrated into existing Flask app +- Accessible from any browser +- No installation required +- Easy to share and collaborate + +**Cons:** +- Requires server to be running +- Limited offline capability + +#### Option 2: Desktop Application +**Framework:** +- **Electron** (JavaScript-based desktop app) + - Wrap the web-based editor + - Direct file system access + - Can bundle Python scripts +- **Tauri** (Rust-based, lighter alternative to Electron) + +**Pros:** +- Better file system integration +- Can work offline +- More desktop-native experience +- Better performance + +**Cons:** +- Requires installation +- More complex deployment +- Additional development effort + +#### Option 3: VS Code Extension +**Framework:** +- VS Code Extension API +- Webview for the visual editor +- Language server for validation + +**Pros:** +- Integrates with existing development environment +- Can use VS Code's existing features (git, etc.) +- Access to VS Code extension marketplace +- Syntax highlighting fallback to JSON + +**Cons:** +- Limited to VS Code users +- Extension development learning curve +- More restrictive UI capabilities + +### Recommended Approach: Web-Based (Option 1) +Most practical for this project given the existing Flask infrastructure. + +## Implementation Phases + +### Phase 1: Core Canvas (MVP) +**Goals:** +- Display existing navigation.json as visual nodes +- Basic drag-and-drop positioning +- Simple click-to-select nodes +- Read-only view to validate the concept + +**Deliverables:** +- Canvas component with nodes +- JSON parser to convert navigation.json to node graph +- Basic visual styling for nodes + +**Estimated Effort:** 20-30 hours + +### Phase 2: Basic Editing +**Goals:** +- Configuration panel for editing node properties +- Add/delete nodes +- Simple one-to-one connections +- Save back to navigation.json + +**Deliverables:** +- Config panel UI +- Node CRUD operations +- JSON serializer to convert graph back to navigation.json +- Save functionality + +**Estimated Effort:** 30-40 hours + +### Phase 3: Advanced Connections +**Goals:** +- Multiple output circles per node +- Button grouping system +- Visual connection management (drag-drop arrows) +- Connection validation + +**Deliverables:** +- Output group configuration UI +- Connection point rendering +- Arrow drawing and management +- Validation logic + +**Estimated Effort:** 25-35 hours + +### Phase 4: Default Values & Variables +**Goals:** +- Default values configuration section +- Variable management system +- Variable autocomplete/suggestions + +**Deliverables:** +- Default values UI +- Variable tracking system +- Smart suggestions + +**Estimated Effort:** 15-20 hours + +### Phase 5: Polish & Features +**Goals:** +- Auto-layout algorithm +- Zoom/pan controls +- Minimap +- Undo/redo +- Export/import +- Validation reporting +- Help tooltips + +**Deliverables:** +- All polish features +- Documentation +- User guide + +**Estimated Effort:** 20-30 hours + +**Total Estimated Effort:** 110-155 hours (3-4 weeks full-time development) + +## Data Structure Mapping + +### Current navigation.json Structure (Runtime Format) +```json +{ + "start": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "answers": [ + { + "caption": "Door", + "image": "🚪", + "next": "q-door-type", + "filter": { "baseType": "Door" } + }, + { + "caption": "Window", + "image": "🪟", + "next": "q-window-type", + "filter": { "baseType": "Window" } + } + ] + }, + "q-door-type": { + "type": "question", + "inputType": "button", + "title": "What type of door?", + "answers": [...] + } +} +``` + +### Project File Structure (Editor Format - `.navproj.json`) +The editor saves this format, which includes all visual layout and editor-specific data: +```json +{ + "version": "1.0", + "metadata": { + "created": "2026-04-02", + "lastModified": "2026-04-02", + "description": "Product Finder Navigation Flow" + }, + "nodes": [ + { + "id": "node-1", + "tileId": "start", + "type": "NavigationTile", + "position": { "x": 100, "y": 200 }, + "config": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "notes": "

Quick Tips...

", + "buttons": [ + { + "caption": "Door", + "image": "🚪", + "outputGroup": "output-door", + "filter": { "baseType": "Door" } + }, + { + "caption": "Window", + "image": "🪟", + "outputGroup": "output-window", + "filter": { "baseType": "Window" } + } + ] + }, + "outputs": [ + { + "id": "output-door", + "color": "#3b82f6", + "label": "Door Path", + "targetTileId": "q-door-type" + }, + { + "id": "output-window", + "color": "#10b981", + "label": "Window Path", + "targetTileId": "q-window-type" + } + ], + "defaults": [] + } + ], + "connections": [ + { + "id": "conn-1", + "from": "node-1", + "fromOutput": "output-door", + "to": "node-2" + }, + { + "id": "conn-2", + "from": "node-1", + "fromOutput": "output-window", + "to": "node-3" + } + ], + "editorSettings": { + "zoom": 1.0, + "panX": 0, + "panY": 0, + "selectedNodeId": null + }, + "customTiles": [ + { + "id": "custom-size-flow", + "name": "Size Selection Flow", + "description": "Handles both standard sizes and custom dimensions", + "icon": "šŸ“", + "version": "1.0", + "entryPoint": "node-size-entry", + "exitPoints": [ + { + "id": "exit-standard", + "label": "Standard Size Selected", + "color": "#3b82f6" + }, + { + "id": "exit-custom", + "label": "Custom Dimensions", + "color": "#10b981" + } + ], + "nodes": [ + { + "id": "node-size-entry", + "tileId": "entry", + "type": "EntryPoint", + "position": { "x": 50, "y": 150 }, + "config": {}, + "outputs": [ + { + "id": "to-size-display", + "targetTileId": "node-size-buttons" + } + ] + }, + { + "id": "node-size-buttons", + "tileId": "size-options", + "type": "NavigationTile", + "position": { "x": 250, "y": 150 }, + "config": { + "title": "Select Size", + "buttons": [ + { + "caption": "30\" x 81\"", + "outputGroup": "standard", + "dimensions": { "width": 30, "height": 81 } + }, + { + "caption": "32\" x 81\"", + "outputGroup": "standard", + "dimensions": { "width": 32, "height": 81 } + }, + { + "caption": "Custom Size", + "outputGroup": "custom" + } + ] + }, + "outputs": [ + { + "id": "standard", + "exitPoint": "exit-standard" + }, + { + "id": "custom", + "targetTileId": "node-dimension-form" + } + ] + }, + { + "id": "node-dimension-form", + "tileId": "custom-dimensions", + "type": "FormTile", + "position": { "x": 450, "y": 250 }, + "config": { + "title": "Enter Custom Dimensions", + "fields": [ + { "name": "width", "type": "number" }, + { "name": "height", "type": "number" } + ] + }, + "outputs": [ + { + "id": "form-complete", + "exitPoint": "exit-custom" + } + ] + } + ], + "connections": [ + { + "from": "node-size-entry", + "to": "node-size-buttons" + }, + { + "from": "node-size-buttons", + "fromOutput": "custom", + "to": "node-dimension-form" + } + ] + } + ] +} +``` + +**Custom Tiles in Project File:** +- **customTiles array**: Stores all reusable sub-flows as separate entities +- **Entry/Exit Points**: Define how the custom tile interfaces with the main flow +- **Internal nodes**: Complete flow definition within the custom tile +- **Versioning**: Track custom tile versions for compatibility +- **Metadata**: Name, description, icon for library display + +When a custom tile is used in the main flow: +```json +{ + "id": "node-5", + "tileId": "use-size-flow", + "type": "CustomTile", + "customTileRef": "custom-size-flow", // References the custom tile + "position": { "x": 500, "y": 300 }, + "outputs": [ + { + "id": "standard-path", + "exitPoint": "exit-standard", // Maps to custom tile's exit + "targetTileId": "results" + }, + { + "id": "custom-path", + "exitPoint": "exit-custom", + "targetTileId": "q-accessories" + } + ] +} +} +``` + +**Key Differences:** +- **Project file** includes: positions, colors, output groups, editor state +- **Navigation file** includes: only runtime data needed by the app +- **Export process** converts project format → navigation format using class export methods +- **Import process** can convert existing navigation.json → project format + +### Tile Class Architecture + +Each tile type is a class with its own export logic: + +```typescript +// Base class for all tiles +abstract class BaseTile { + id: string; + tileId: string; + position: Position; + config: any; + outputs: Output[]; + defaults: DefaultValue[]; + + // Each tile type implements its own export logic + abstract export(): object; + + // Validate tile configuration before export + abstract validate(): ValidationResult; +} + +// Navigation tile - standard question with buttons +class NavigationTile extends BaseTile { + export(): object { + return { + type: this.config.type, + inputType: this.config.inputType, + title: this.config.title, + subtitle: this.config.subtitle, + notes: this.config.notes, + answers: this.config.buttons.map(btn => ({ + caption: btn.caption, + image: btn.image, + next: this.outputs.find(o => o.id === btn.outputGroup)?.targetTileId, + filter: btn.filter, + dimensions: btn.dimensions + })) + }; + } +} + +// Form tile - custom input fields +class FormTile extends BaseTile { + export(): object { + return { + type: "question", + inputType: "form", + title: this.config.title, + subtitle: this.config.subtitle, + notes: this.config.notes, + measurementType: this.config.measurementType, + fields: this.config.fields, + next: this.outputs[0]?.targetTileId || "results" + }; + } +} + +// Results tile - terminal node +class ResultsTile extends BaseTile { + export(): object { + return { + type: "results", + title: this.config.title, + // No outputs - this is terminal + }; + } +} + +// Custom tile - references a reusable sub-flow +class CustomTile extends BaseTile { + customTileRef: string; // ID of custom tile definition + customTileDef: CustomTileDefinition; // Reference to the actual custom tile + + export(): object { + // Custom tiles export by "flattening" their internal flow + // OR by exporting as a single node that gets expanded at runtime + + // Option A: Flatten (embed internal nodes into main navigation) + // This makes the exported JSON have no "custom tile" concept + const flattened = {}; + + // Generate unique tile IDs for internal nodes + this.customTileDef.nodes.forEach(node => { + const uniqueId = `${this.id}_${node.tileId}`; + flattened[uniqueId] = node.export(); + }); + + // Wire entry point to first internal node + const entryNode = this.customTileDef.entryPoint; + // Wire exit points back to main flow + + return flattened; + + // Option B: Keep as reference (requires app to support custom tiles) + // return { + // type: "customTile", + // ref: this.customTileRef, + // outputs: this.outputs.map(o => ({ + // exitPoint: o.exitPoint, + // next: o.targetTileId + // })) + // }; + } + + validate(): ValidationResult { + const errors = []; + + // Validate all outputs map to exit points + this.outputs.forEach(output => { + const exitExists = this.customTileDef.exitPoints.find( + ep => ep.id === output.exitPoint + ); + if (!exitExists) { + errors.push(`Output references non-existent exit point: ${output.exitPoint}`); + } + }); + + // Validate internal flow is complete + const internalValidation = this.customTileDef.validate(); + errors.push(...internalValidation.errors); + + return { + valid: errors.length === 0, + errors + }; + } +} +``` + +### Export Workflow + +```typescript +class NavigationProject { + nodes: BaseTile[]; + connections: Connection[]; + + // Export entire project to navigation.json + exportToNavigation(): object { + const navigation: any = {}; + + // Each node exports itself to JSON + this.nodes.forEach(node => { + navigation[node.tileId] = node.export(); + }); + + return navigation; + } + + // Import existing navigation.json to project format + static importFromNavigation(navData: object): NavigationProject { + const project = new NavigationProject(); + const layout = new AutoLayout(); // Auto-position nodes + + Object.entries(navData).forEach(([tileId, data], index) => { + const position = layout.getNextPosition(index); + const tile = this.createTileFromData(tileId, data, position); + project.nodes.push(tile); + }); + + // Reconstruct connections from 'next' references + project.reconstructConnections(); + + return project; + } + + // Validate entire project before export + validate(): ValidationReport { + const report = new ValidationReport(); + + this.nodes.forEach(node => { + const result = node.validate(); + if (!result.isValid) { + report.addError(node.tileId, result.errors); + } + }); + + // Check for orphaned nodes + const connectedNodes = new Set( + this.connections.flatMap(c => [c.from, c.to]) + ); + this.nodes.forEach(node => { + if (!connectedNodes.has(node.id) && node.tileId !== 'start') { + report.addWarning(node.tileId, 'Node is not connected'); + } + }); + + return report; + } +} +``` + +### File Operations + +```typescript +// Save project file +async function saveProject(project: NavigationProject, filepath: string) { + const projectData = { + version: "1.0", + metadata: { + created: project.metadata.created, + lastModified: new Date().toISOString(), + description: project.metadata.description + }, + nodes: project.nodes.map(n => n.toJSON()), + connections: project.connections, + editorSettings: project.editorSettings + }; + + await fs.writeFile(filepath, JSON.stringify(projectData, null, 2)); +} + +// Export to navigation.json +async function exportNavigation(project: NavigationProject, filepath: string) { + // Validate first + const validation = project.validate(); + if (!validation.isValid) { + throw new Error(`Validation failed: ${validation.errors.join(', ')}`); + } + + // Export + const navData = project.exportToNavigation(); + await fs.writeFile(filepath, JSON.stringify(navData, null, 2)); +} + +// Load project file +async function loadProject(filepath: string): Promise { + const data = JSON.parse(await fs.readFile(filepath, 'utf-8')); + return NavigationProject.fromJSON(data); +} +``` +``` + +## Key Challenges & Solutions + +### Challenge 1: Complex Button Grouping +**Problem:** Multiple buttons can map to different outputs, and the structure can get complex. + +**Solution A: Color-Coded Output Groups** (Recommended) +- Each output path gets a unique color (suggested max ~5 colors per tile) +- User can set/choose the color for each output group +- Buttons using that output show: + - Colored rect/border around the button in the config panel + - Optional: button background tint in that color (browser support varies) +- Connection arrows from the tile use the corresponding color +- Provides immediate visual clarity for which buttons lead where + +**Solution B: Per-Button Output Selector** +- Each button in the config panel has an "Output Group" dropdown near it +- Dropdown shows: + - Existing output groups (with their target tiles) + - "Add New Output" option +- Creates output circles on-demand based on unique selections +- More flexible but requires more user interactions per button + +**Config Panel Structure (Type-Specific Fields):** +``` +Button Type: [navigate ā–¼] + +IF navigate: + Output Group: [🟢 Color-1: Next Step ā–¼] (Add New) + Target Tile: [Select Storm Door Size ā–¼] + Button Label: [Field] + ... other options + +IF toggle: + Toggle Group: [☐ Group A ā˜‘ Group B ☐ Group C] (Add New Toggle Group) + Variable to Set: [doorType ā–¼] + Value or Label: [Field] + ... other options +``` + +**Visual Representation with Colored Outputs:** +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Door Type │ +│ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ 🟢 (Green - Predefined route) +│ │ Standard ā”‚ā”€ā”€ā”€ā”¼ā”€ā”€šŸŸ¢ +│ │ Custom ā”‚ā”€ā”€ā”€ā”¼ā”€ā”€šŸ”µ +│ │ Skip ā”‚ā”€ā”€ā”€ā”¼ā”€ā”€šŸ”“ +│ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ šŸ”µ (Blue - Custom route) +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ šŸ”“ (Red - Skip to end) +``` + +### Challenge 2: Maintaining JSON Compatibility +**Problem:** The visual editor creates its own structure, but must export to the existing navigation.json format. + +**Solution: Class-Based Export Architecture** + +Each tile in the visual editor is a class object with an export function that generates clean JSON. This creates two separate file types: + +#### 1. Project File (`.navproj.json` or similar) +Saved/loaded by the editor, contains all editor-specific data: +```json +{ + "version": "1.0", + "metadata": { + "created": "2026-04-02", + "lastModified": "2026-04-02", + "author": "User" + }, + "nodes": [ + { + "id": "node-1", + "tileId": "start", + "type": "NavigationTile", + "position": { "x": 100, "y": 200 }, + "config": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "notes": "

Quick Tips:...

", + "buttons": [ + { + "caption": "Door", + "image": "🚪", + "outputGroup": "output-1", + "filter": { "baseType": "Door" } + }, + { + "caption": "Window", + "image": "🪟", + "outputGroup": "output-2", + "filter": { "baseType": "Window" } + } + ] + }, + "outputs": [ + { + "id": "output-1", + "color": "#3b82f6", + "label": "Door Path", + "targetTileId": "q-door-type" + }, + { + "id": "output-2", + "color": "#10b981", + "label": "Window Path", + "targetTileId": "q-window-type" + } + ], + "defaults": [] + } + ], + "connections": [ + { + "id": "conn-1", + "from": "node-1", + "fromOutput": "output-1", + "to": "node-2" + } + ], + "editorSettings": { + "zoom": 1.0, + "panX": 0, + "panY": 0 + } +} +``` + +#### 2. Export File (`navigation.json`) +Generated from project file during export, clean JSON for runtime: +```json +{ + "start": { + "type": "question", + "inputType": "button", + "title": "What are you looking for?", + "subtitle": "Select the product category", + "notes": "

Quick Tips:...

", + "answers": [ + { + "caption": "Door", + "image": "🚪", + "next": "q-door-type", + "filter": { + "baseType": "Door" + } + }, + { + "caption": "Window", + "image": "🪟", + "next": "q-window-type", + "filter": { + "baseType": "Window" + } + } + ] + } +} +``` + +#### Export Function Example (TypeScript/JavaScript) +```typescript +class NavigationTile { + id: string; + config: TileConfig; + outputs: Output[]; + + // Export this tile to navigation.json format + export(): object { + const navigationNode: any = { + type: this.config.type, + inputType: this.config.inputType, + title: this.config.title, + subtitle: this.config.subtitle + }; + + // Add optional fields + if (this.config.notes) { + navigationNode.notes = this.config.notes; + } + + // Convert buttons with output groups to answers with next references + if (this.config.buttons) { + navigationNode.answers = this.config.buttons.map(button => { + const output = this.outputs.find(o => o.id === button.outputGroup); + return { + caption: button.caption, + image: button.image, + next: output?.targetTileId || null, + filter: button.filter + }; + }); + } + + // Add conditional logic if present + if (this.config.conditional) { + navigationNode.conditional = this.config.conditional; + } + + return navigationNode; + } + + // Import from navigation.json to project format + static import(tileId: string, data: any, position: Position): NavigationTile { + // Convert navigation.json format back to editor format + // ... + } +} + +// Export entire project to navigation.json +function exportToNavigation(project: ProjectFile): object { + const navigation: any = {}; + + project.nodes.forEach(node => { + const tile = new NavigationTile(node); + navigation[node.tileId] = tile.export(); + }); + + return navigation; +} +``` + +#### Benefits of This Approach: +āœ… **Separation of Concerns** - Editor data (positions, colors) separate from runtime data +āœ… **Clean Exports** - Generated navigation.json contains only what the app needs +āœ… **Version Control Friendly** - Project file can track editor-specific changes +āœ… **Validation** - Export process can validate each tile before generating JSON +āœ… **Backwards Compatible** - Can import existing navigation.json files +āœ… **Extensible** - Easy to add new tile types with their own export logic + +#### Workflow: +1. **Edit Mode**: User works with project file (`.navproj.json`) +2. **Export**: Click "Export" → validates and generates `navigation.json` +3. **Import**: Can load existing `navigation.json` → converts to project format +4. **Save/Load**: Project file preserves all editor state between sessions + +### Challenge 3: Large Navigation Flows & Reusability +**Problem:** Complex products might have 50+ questions, making the canvas crowded. Common patterns (size selection, color/material combos) are duplicated across flows. + +**Solution: Hierarchical Sub-Flows (Custom Tiles)** + +Implement a **nested flow architecture** similar to Node-RED sub-flows, LabVIEW SubVIs, or PLC function blocks used in industrial automation systems. + +#### Concept: Custom Tiles +1. **Create a Sub-Flow**: Select 3-5 related tiles and group them into a reusable sub-flow +2. **Save as Custom Tile**: Name it (e.g., "Size Selection Flow", "Material-Color Picker") +3. **Use in Main Flow**: The custom tile appears as a single node with defined inputs/outputs +4. **Drill Down to Edit**: Double-click the custom tile to open its internal flow in a separate view +5. **Propagate Changes**: Edits to the custom tile automatically update all instances + +#### Example Use Cases + +**Size Selection Flow:** +``` +[Entry Point] + ↓ +[Show Standard Sizes] ──→ [Size Button 1] → [Exit: Standard] + ──→ [Size Button 2] → [Exit: Standard] + ──→ [Size Button 3] → [Exit: Standard] + ──→ [Custom Size] → [Dimension Form] → [Exit: Custom] +``` + +This 5-tile flow becomes **one custom tile** in the main flow: +``` +[Color Selection] → [Size Selection Flow] → [Results] +``` + +**Material-Color Combo Flow:** +``` +[Entry Point] + ↓ +[Material Selection] → [Aluminum] → [Aluminum Colors] → [Exit] + → [Vinyl] → [Vinyl Colors] → [Exit] + → [Wood] → [Wood Stains] → [Exit] +``` + +**Glass Options Flow:** +``` +[Entry Point] + ↓ +[Glass Type] → [Clear] → [Thickness Options] → [Exit] + → [Tinted] → [Tint Colors] → [Exit] + → [Obscure] → [Pattern Selection] → [Exit] +``` + +#### UI/UX for Sub-Flows + +**Main Flow View:** +- Custom tiles show with special icon (e.g., šŸ“¦ or collapsed group icon) +- Display name: "Size Selection Flow" +- Show entry point (green circle) and exit points (colored circles per output group) +- Quick preview badge: "5 nodes" + +**Switching Views:** +- **Option A:** Dropdown menu at top: `Main Flow ā–¼` → Select custom tile name to switch +- **Option B:** Double-click custom tile to drill down into its internal flow +- **Option C:** Breadcrumb navigation: `Main Flow > Size Selection Flow` + +**Inside Custom Tile View:** +- Special entry node: "Entry Point" (where main flow enters) +- Special exit nodes: "Exit: Standard", "Exit: Custom" (where flow returns to main) +- Edit the internal flow just like the main flow +- Changes save automatically + +#### Custom Tile Library +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ Custom Tiles Library │ +ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤ +│ šŸ“¦ Size Selection Flow │ [Edit] [Delete] +│ šŸ“¦ Material-Color Combo │ [Edit] [Delete] +│ šŸ“¦ Glass Options Flow │ [Edit] [Delete] +│ │ +│ [+ Create New Custom Tile] +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +#### Implementation Benefits + +āœ… **Reusability** - Create once, use many times across different product flows +āœ… **Maintainability** - Update the custom tile once, all instances update automatically +āœ… **Complexity Management** - Main flow stays clean; complex logic hidden in sub-flows +āœ… **Organization** - Group related questions logically +āœ… **Scalability** - Handle 100+ node flows by keeping main flow at 10-20 high-level tiles +āœ… **Collaboration** - Team members can work on different custom tiles independently + +#### Additional Organization Features +- **Minimap** for navigation in large flows +- **Search/filter** to find specific nodes or custom tiles +- **Auto-layout** algorithm to organize nodes cleanly +- **Zoom controls** (fit to screen, zoom to selection) +- **Layers or tags** for categorizing nodes (e.g., "Storm Doors", "Patio Doors") + +### Challenge 4: Path Validation +**Problem:** Ensuring all paths lead somewhere valid and no orphaned nodes. + +**Solution:** +- Real-time validation as user edits +- Visual indicators: red border on nodes with issues +- Validation report showing: + - Orphaned nodes (no incoming connections) + - Dead ends (no outgoing connections) + - Missing required fields + - Circular references +- Test mode: simulate walking through the navigation + +## User Experience Flow + +### Creating a New Node +1. Click "Add Node" button or drag from palette +2. Node appears on canvas with default settings +3. User clicks node to select +4. Config panel opens on right +5. User fills in: Question ID, title, type +6. Based on type, additional fields appear (buttons, input fields, etc.) +7. User configures output groups +8. User saves (auto or manual) +9. Node updates visually on canvas + +### Connecting Two Nodes +1. User selects source node +2. Output circles appear on node edges +3. User clicks and drags from a circle +4. Arrow follows mouse cursor +5. User drops on target node +6. Connection is created +7. Arrow persists showing the connection +8. Clicking arrow allows editing connection details + +### Setting Default Values +1. User selects a node +2. Config panel shows "Default Values" section +3. User clicks "[+ Add Default Value]" +4. New row appears with dropdowns and field +5. User selects variable from dropdown (or types new name) +6. User enters value in field +7. Value is stored with the node +8. When navigation reaches this node, values are set + +### Testing the Navigation +1. User clicks "Test Mode" button +2. Canvas switches to interactive mode +3. Simulated UI appears showing the current question +4. User can answer questions, seeing the path highlighted +5. Path taken is shown with animated arrows +6. User can reset and try different paths +7. Exit test mode to return to editing + +## Additional Features to Consider + +### Versioning & History +- Save snapshots of navigation.json before changes +- Git integration for version control +- Undo/redo with unlimited history +- Compare versions side-by-side + +### Collaboration +- Multi-user editing with real-time sync +- Comments on nodes for team discussions +- Lock nodes being edited by others +- Change notifications + +### Import/Export +- Export to different formats (JSON, YAML, diagram image) +- Import from other tools or formats +- Export to documentation (PDF, HTML) + +### Templates & Snippets +- Save common question patterns as templates +- Library of pre-built question flows +- Duplicate and modify existing flows + +### Analytics Integration +- Show usage statistics on nodes (if tracking is implemented) +- Highlight most/least used paths +- Identify bottlenecks or drop-off points + +### Accessibility +- Keyboard shortcuts for all operations +- Screen reader support +- High contrast mode +- Zoom accessibility + +## Additional Considerations & Potential Issues + +### Critical Features Not Yet Detailed + +#### 1. Undo/Redo System āš ļø HIGH PRIORITY +**Why Critical:** Visual editors require robust undo/redo because users make lots of experimental changes, accidental deletions, and need quick recovery from mistakes. + +**Implementation Approach:** +- **Command Pattern**: Each action (move node, add connection, edit property) is a reversible command +- **Action History Stack**: Maintain array of completed actions, track current position +- **Supported Actions**: + - Node: Create, Delete, Move, Duplicate, Edit Properties + - Connection: Add, Remove, Reroute + - Group Operations: Multi-node moves, bulk edits +- **UI**: Ctrl+Z (undo), Ctrl+Y/Ctrl+Shift+Z (redo), visual history panel (optional) +- **State Management**: Snapshot approach vs incremental changes + - **Snapshot**: Save full state after each action (simple but memory-intensive) + - **Incremental**: Store delta changes (complex but efficient) + +**Recommendation:** Start with snapshot approach for MVP, optimize to incremental if performance becomes an issue. + +**Estimated Effort:** 10-15 hours + +--- + +#### 2. Copy/Paste & Duplication +**Why Important:** Users frequently need to duplicate similar question nodes, copy patterns, or reuse configurations. + +**Features Needed:** +- **Copy Single Node**: Ctrl+C copies selected node and its config +- **Copy Multiple Nodes**: Shift-click or drag-select to copy groups +- **Copy with Connections**: Option to include/exclude connection information +- **Paste**: Ctrl+V pastes at cursor position or offset from original +- **Duplicate**: Ctrl+D creates copy immediately (no clipboard) +- **Between Flows**: Copy from main flow, paste into custom tile (and vice versa) +- **ID Conflict Resolution**: Auto-generate new unique IDs for pasted nodes +- **Connection Handling**: + - Internal connections (between pasted nodes) are preserved + - External connections (to non-copied nodes) are broken/cleared + +**Clipboard Format:** +```json +{ + "type": "copilot-nav-editor", + "version": "1.0", + "nodes": [...], + "connections": [...], + "boundingBox": { "width": 500, "height": 300 } +} +``` + +**Estimated Effort:** 8-12 hours + +--- + +#### 3. Keyboard Shortcuts +**Why Important:** Power users expect keyboard efficiency. Mouse-only interfaces feel slow after extended use. + +**Essential Shortcuts:** +``` +Navigation & Selection: + Tab / Shift+Tab - Select next/previous node + Arrow Keys - Move selected node (hold Shift for 10px increments) + Ctrl+A - Select all nodes + Ctrl+Click - Multi-select nodes + Shift+Click - Select range between last selected + +Editing: + Ctrl+Z - Undo + Ctrl+Y / Ctrl+Shift+Z - Redo + Ctrl+C - Copy + Ctrl+V - Paste + Ctrl+X - Cut + Ctrl+D - Duplicate + Delete / Backspace - Delete selected + Enter - Edit selected node (open config panel) + Esc - Deselect / Close panel / Cancel drag + +View: + Spacebar (hold) - Pan/Hand tool (drag canvas) + Ctrl+0 - Zoom to fit all + Ctrl++ / Ctrl+- - Zoom in/out + Ctrl+Scroll - Zoom at cursor + Ctrl+F - Search/Find node + +File Operations: + Ctrl+S - Save project + Ctrl+Shift+S - Save As + Ctrl+E - Export to navigation.json + Ctrl+N - New project + Ctrl+O - Open project +``` + +**Implementation:** Hook keyboard events at canvas level, prevent conflicts with browser shortcuts. + +**Estimated Effort:** 5-8 hours + +--- + +#### 4. Connection Routing Algorithm +**Why Important:** As flows grow complex, straight-line arrows overlap nodes and cross each other, creating visual mess. + +**Options:** + +**Option A: Straight Lines (Simple)** +- Direct point-to-point lines +- āœ… Easy to implement +- āœ… Fast rendering +- āŒ Overlaps and crosses in complex flows + +**Option B: Bezier Curves (Smooth)** +- Curved connections using cubic Bezier paths +- āœ… Visually appealing +- āœ… Some automatic avoidance via control points +- āš ļø Can still overlap nodes +- **React Flow default option** + +**Option C: Orthogonal Routing (Clean)** +- Only horizontal and vertical lines (like electrical diagrams) +- āœ… Very clean, professional look +- āœ… Better node avoidance possible +- āŒ More complex algorithm +- āŒ May require more space + +**Option D: Intelligent Routing (Advanced)** +- Pathfinding algorithm (A*) to route around nodes +- āœ… Best visual clarity +- āœ… No overlaps +- āŒ Most complex +- āŒ Performance considerations with many connections + +**Recommendation for MVP:** Start with Bezier curves (React Flow default), add orthogonal routing in Phase 5 if needed. + +**Estimated Effort:** +- Bezier (built-in): 0 hours +- Orthogonal: 15-20 hours +- Intelligent: 30-40 hours + +--- + +#### 5. Auto-Save vs Manual Save +**Why Important:** Browser crashes, accidental closures, and forgotten saves can lose hours of work. + +**Recommended Strategy: Hybrid Approach** + +**Auto-Save to Local Storage:** +- Save to `localStorage` every 30 seconds (debounced after changes) +- Keep last 3 auto-save versions with timestamps +- Recover on next load if detected +- āš ļø localStorage limit: ~5-10MB (should be sufficient for most flows) + +**Manual Save to File:** +- Ctrl+S saves project file to disk (`.navproj.json`) +- Visual indicator: "Saved" checkmark vs "Unsaved changes" dot +- Prompt before closing window if unsaved changes exist + +**Cloud Sync (Future):** +- Optional integration with cloud storage (Google Drive, Dropbox, OneDrive) +- Version history and conflict resolution + +**UI Feedback:** +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ 🟢 Auto-saved 30 seconds ago │ +│ ⚫ Unsaved manual changes │ +│ [Save Project] [Export Navigation] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +**Browser Close Warning:** +```javascript +window.addEventListener('beforeunload', (e) => { + if (hasUnsavedChanges()) { + e.preventDefault(); + e.returnValue = 'You have unsaved changes. Are you sure?'; + } +}); +``` + +**Estimated Effort:** 6-10 hours + +--- + +#### 6. Performance at Scale āš ļø IMPORTANT +**Why Important:** A navigation with 100+ nodes and 200+ connections could slow down rendering and interactions. + +**Potential Issues:** +- Slow drag-and-drop response +- Lag when adding/removing connections +- Sluggish panning and zooming +- Memory consumption + +**React Flow Performance Characteristics:** +- Generally handles 100-300 nodes well +- Slows down around 500+ nodes +- Connections are more performant than nodes + +**Optimization Strategies:** + +**Level 1: Built-in React Flow Optimizations** (Free) +- Virtual rendering (only render visible nodes) +- Memoization of node components +- Connection edge optimization + +**Level 2: Custom Optimizations** (If Needed) +- **Lazy Load Custom Tiles**: Don't load internal nodes until opened +- **Connection Simplification**: Reduce curve resolution for distant connections +- **Level of Detail**: Show simplified node representation when zoomed out +- **Pagination/Filtering**: Show subset of nodes at a time (by category, search) +- **Canvas Sectioning**: Divide large flows into "pages" or "zones" + +**Performance Testing Plan:** +1. Create test navigation with 50, 100, 200, 500 nodes +2. Measure frame rate during drag operations (target: 60 FPS) +3. Measure zoom/pan responsiveness +4. Measure initial load time +5. Test on lower-end hardware + +**Recommendation:** Test with realistic data size early (Phase 2), optimize only if needed. + +**Estimated Effort:** +- Testing: 3-5 hours +- Optimizations: 10-20 hours (if needed) + +--- + +#### 7. Circular Dependency Detection +**Why Important:** Node A → Node B → Node C → Node A creates infinite loops, breaking navigation. + +**Detection Algorithm:** +```typescript +function detectCycles(nodes: Node[], connections: Connection[]): string[] { + const cycles: string[] = []; + + // Build adjacency list + const graph = buildGraph(connections); + + // Depth-first search with path tracking + const visited = new Set(); + const recursionStack = new Set(); + + function dfs(nodeId: string, path: string[]): boolean { + visited.add(nodeId); + recursionStack.add(nodeId); + path.push(nodeId); + + const neighbors = graph.get(nodeId) || []; + for (const neighbor of neighbors) { + if (!visited.has(neighbor)) { + if (dfs(neighbor, path)) return true; + } else if (recursionStack.has(neighbor)) { + // Cycle detected! + const cycleStart = path.indexOf(neighbor); + const cycle = path.slice(cycleStart).concat(neighbor); + cycles.push(cycle.join(' → ')); + return true; + } + } + + recursionStack.delete(nodeId); + return false; + } + + // Check all nodes + nodes.forEach(node => { + if (!visited.has(node.id)) { + dfs(node.id, []); + } + }); + + return cycles; +} +``` + +**User Experience:** +- **Real-time Detection**: Run cycle check after each connection change +- **Visual Warning**: Highlight nodes involved in cycle with red border +- **Error Message**: "Cycle detected: Start → Door Type → Color → Start" +- **Suggestion**: "This creates an infinite loop. Remove one of these connections." +- **Prevent Export**: Don't allow exporting navigation with cycles + +**Estimated Effort:** 4-6 hours + +--- + +#### 8. Backwards Compatibility Strategy +**Why Important:** As the `.navproj.json` format evolves (v1.0 → v1.1 → v2.0), old project files must still load. + +**Versioning Strategy:** +```json +{ + "version": "1.2", + "formatVersion": "1.2", // Semantic versioning + "nodes": [...] +} +``` + +**Migration System:** +```typescript +const migrations = { + "1.0-to-1.1": (data) => { + // Add default values structure if missing + data.nodes.forEach(node => { + if (!node.defaults) node.defaults = []; + }); + return data; + }, + "1.1-to-1.2": (data) => { + // Add custom tiles structure + if (!data.customTiles) data.customTiles = []; + return data; + } +}; + +function migrateProjectFile(data: any): any { + const currentVersion = data.version || "1.0"; + const targetVersion = "1.2"; + + // Apply migrations sequentially + let migrated = data; + const path = getMigrationPath(currentVersion, targetVersion); + + for (const step of path) { + migrated = migrations[step](migrated); + } + + migrated.version = targetVersion; + return migrated; +} +``` + +**Loading Process:** +1. Detect version in loaded file +2. If old version, show: "Upgrading project from v1.0 to v1.2..." +3. Apply migrations sequentially +4. Create backup of original file before overwriting +5. Ask user to save upgraded version + +**Testing:** +- Keep example projects from each version +- Automated tests to ensure all old versions load correctly + +**Estimated Effort:** 5-8 hours (initial setup), 2-3 hours per future migration + +--- + +#### 9. Error Handling & Recovery +**Why Important:** Corrupted files, network issues, browser crashes require graceful handling. + +**Scenarios & Solutions:** + +**Corrupted Project File:** +```typescript +function loadProject(filepath: string): Result { + try { + const data = JSON.parse(fileContent); + + // Schema validation + const validation = validateProjectSchema(data); + if (!validation.valid) { + return Error(`Invalid project structure: ${validation.errors}`); + } + + return Ok(Project.fromJSON(data)); + + } catch (e) { + // JSON parse error + if (autoSaveExists()) { + return showRecoveryDialog(); + } else { + return Error("Could not load project. File may be corrupted."); + } + } +} +``` + +**Auto-Save Recovery Dialog:** +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ āš ļø Project File Could Not Be Loaded │ +│ │ +│ The file may be corrupted. However, we │ +│ found auto-saved versions: │ +│ │ +│ šŸ”µ Auto-save from 2 minutes ago │ +│ šŸ”µ Auto-save from 5 minutes ago │ +│ šŸ”µ Auto-save from 10 minutes ago │ +│ │ +│ [Recover] [Cancel] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +**Export Validation Error:** +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ āŒ Cannot Export - Validation Errors │ +│ │ +│ The following issues must be fixed: │ +│ │ +│ • Node "q-dimensions" has no connections │ +│ • Cycle detected: start → door → start │ +│ • Node "q-color" missing required title │ +│ │ +│ [Show Details] [Close] │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +**Backup Strategy:** +- Before any save operation, create backup: `project.navproj.backup.json` +- Keep last 3 backups rotating +- Manual "Restore from Backup" option in File menu + +**Estimated Effort:** 8-12 hours + +--- + +#### 10. Search/Filter in Canvas +**Why Important:** Finding specific nodes in a 50+ node flow is time-consuming. + +**Search Features:** + +**Quick Search (Ctrl+F):** +``` +ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” +│ šŸ” Search: door___ │ +│ │ +│ Results (3): │ +│ • q-door-type (Question) │ +│ • q-door-size (Form) │ +│ • door-accessories (Results) │ +ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ +``` + +**Search Criteria:** +- Node title +- Node ID (tileId) +- Question type +- Button captions +- Notes content + +**Results Interaction:** +- Click result → zoom to node and highlight +- Arrow down/up to cycle through results +- Esc to close search + +**Advanced Filter:** +``` +Filter Nodes: +ā˜‘ Questions ā˜‘ Forms ā˜‘ Results ā˜‘ Custom Tiles +ā˜‘ Connected ☐ Orphaned +Text: [ ] +``` + +**Visual Highlighting:** +- Matching nodes: bright blue border +- Dimmed view: non-matching nodes become semi-transparent +- Match count badge: "3 of 47 nodes match" + +**Estimated Effort:** 6-10 hours + +--- + +### Nice-to-Have Features + +#### 11. Comments & Annotations +**Use Case:** Team collaboration, leaving TODO notes, explaining complex logic. + +**Implementation:** +- **Node-level Notes**: Add "Notes" field in config panel (already exists in data) +- **Canvas Comments**: Floating text boxes on canvas (like sticky notes) +- **Connection Labels**: Add text labels to connection arrows +- **Visual Indicators**: šŸ’¬ icon on nodes with comments + +**Estimated Effort:** 5-8 hours + +--- + +#### 12. Grouping/Visual Containers +**Use Case:** Visually organize related nodes without creating a custom tile. + +**Implementation:** +- **Background Rectangle**: Dotted/dashed border around group of nodes +- **Group Label**: Title at top of container +- **Color Coding**: Different colors for different product categories +- **Collapse/Expand**: Optional ability to collapse group to single icon +- **Move Group**: Drag group title to move all contained nodes + +**Example:** +``` +╭─ Storm Door Questions ──────────────╮ +│ │ +│ [Color] → [Size] → [Accessories] │ +│ │ +╰─────────────────────────────────────╯ +``` + +**Estimated Effort:** 10-15 hours + +--- + +#### 13. Diff/Compare View +**Use Case:** Understanding what changed between versions, code review, debugging. + +**Implementation:** +- **Side-by-side Canvas**: Show two versions simultaneously +- **Visual Diff Indicators**: + - 🟢 Green: New nodes + - šŸ”“ Red: Deleted nodes + - 🟔 Yellow: Modified nodes + - Updated connections shown with dashed lines +- **Change Summary**: "5 nodes added, 3 modified, 1 deleted, 7 connections changed" + +**Estimated Effort:** 15-20 hours + +--- + +#### 14. Accessibility +**Why Important:** Legal compliance (ADA/WCAG), inclusive design. + +**Requirements:** + +**Keyboard Navigation:** +- Tab through all interactive elements +- Arrow keys to navigate between nodes +- Enter to activate/edit +- Full functionality without mouse + +**Screen Reader Support:** +- Proper ARIA labels on all elements +- Announce node selections +- Describe connections meaningfully +- Accessible forms in config panel + +**Visual Accessibility:** +- High contrast mode +- Color-blind friendly palette options +- Configurable font sizes +- Focus indicators (visible keyboard focus) + +**Estimated Effort:** 15-25 hours + +--- + +#### 15. Testing/Debugging Mode (Enhanced Details) +**Why Important:** Validate navigation logic before deployment, catch errors early. + +**Features:** + +**Interactive Simulation:** +- Start at "start" node +- Display actual question UI as users would see it +- Click buttons/fill forms to progress +- Visual path highlighting shows route taken + +**Breakpoints:** +- Set breakpoints on specific nodes +- Execution pauses when reached +- Inspect current state + +**Variable Inspector:** +- Show all current variable values +- Display filters applied +- Show which products match current filter + +**Path History:** +``` +Path Taken: +1. start → Door +2. q-door-type → Storm Door +3. q-color → White +4. q-dimensions → Width: 32, Height: 81 +5. results → 3 products match +``` + +**Test Cases:** +- Save common test scenarios +- Replay to verify behavior after changes +- Automated regression testing + +**Estimated Effort:** 20-30 hours + +--- + +#### 16. Export to Diagram Image +**Use Case:** Documentation, presentations, stakeholder communication. + +**Export Formats:** +- **PNG**: Raster image, good for presentations +- **SVG**: Vector image, scalable, good for print +- **PDF**: Multi-page for large flows + +**Export Options:** +- Full canvas or selected region +- Current viewport only +- Include/exclude connection labels +- Background color/transparency +- Resolution/DPI settings + +**Implementation:** +```typescript +function exportToPNG(canvas: CanvasElement, options: ExportOptions) { + const svgData = canvas.toSVG(); + const canvas2d = createCanvas(options.width, options.height); + const ctx = canvas2d.getContext('2d'); + + // Render SVG to canvas + renderSVGToCanvas(svgData, ctx); + + // Export as PNG + return canvas2d.toDataURL('image/png'); +} +``` + +**Estimated Effort:** 8-12 hours + +--- + +### Security & Collaboration Concerns + +#### 17. Multi-User Collaboration (Future Consideration) +**Why Important:** Teams working together on complex flows. + +**Challenges:** +- **Simultaneous Edits**: Two people editing same node +- **Conflict Resolution**: Merge conflicts like Git +- **Real-time Sync**: Show others' cursors and selections +- **Locking**: Prevent conflicts by locking edited nodes + +**Implementation Options:** +- **Operational Transform** (Google Docs approach) +- **CRDT** (Conflict-Free Replicated Data Types) +- **Last-Write-Wins** (simplest, but loses data) + +**Recommendation:** **Not for MVP**. Add in Phase 6+ if team collaboration is critical. + +**Estimated Effort:** 80-120 hours (complex) + +--- + +#### 18. Validation & Security +**Why Important:** Prevent malicious or corrupted files from breaking the editor. + +**Validation on Load:** +```typescript +function validateProjectFile(data: any): ValidationResult { + const errors: string[] = []; + + // Schema validation (JSON Schema) + if (!data.version) errors.push("Missing version"); + if (!data.nodes || !Array.isArray(data.nodes)) { + errors.push("Invalid nodes structure"); + } + + // Security checks + data.nodes?.forEach(node => { + // Sanitize HTML in notes field + if (node.config?.notes) { + node.config.notes = sanitizeHTML(node.config.notes); + } + + // Check for excessively large data + const nodeSize = JSON.stringify(node).length; + if (nodeSize > 100000) { + errors.push(`Node ${node.id} exceeds size limit`); + } + }); + + // File size limit (prevent DoS) + const totalSize = JSON.stringify(data).length; + if (totalSize > 10000000) { // 10MB limit + errors.push("Project file too large"); + } + + return { + valid: errors.length === 0, + errors + }; +} +``` + +**HTML Sanitization:** +- Use library like DOMPurify for notes field +- Prevent XSS attacks via malicious HTML + +**Estimated Effort:** 5-8 hours + +--- + +## Implementation Priority Recommendations + +### Phase 1 (MVP Must-Haves): +- āœ… Core canvas with drag-drop nodes +- āœ… Basic config panel +- āœ… Connections (simple) +- āœ… Save/Load project files +- āœ… Export to navigation.json +- āž• **Undo/Redo** (even basic implementation) +- āž• **Keyboard shortcuts** (at least Ctrl+Z, Ctrl+S, Delete) +- āž• **Auto-save to localStorage** +- āž• **Error handling for corrupted files** + +**Estimated:** 40-50 hours + +--- + +### Phase 2 (Important for Usability): +- āœ… Button grouping & output colors +- āœ… Default values configuration +- āœ… Connection routing (Bezier curves) +- āž• **Copy/Paste** +- āž• **Full keyboard shortcuts** +- āž• **Canvas search** +- āž• **Circular dependency detection** +- āž• **Basic validation & error messages** + +**Estimated:** 50-65 hours + +--- + +### Phase 3 (Advanced Features): +- āœ… Custom tiles (sub-flows) +- āœ… Hierarchical navigation +- āž• **Performance testing & optimization** (if needed) +- āž• **Backwards compatibility system** +- āž• **Testing/debugging mode** +- āž• **Auto-layout algorithm** + +**Estimated:** 50-70 hours + +--- + +### Phase 4 (Polish & Optional): +- āž• **Comments & annotations** +- āž• **Grouping/containers** +- āž• **Export to image** +- āž• **Accessibility improvements** +- āž• **Diff/compare view** +- āž• **Advanced routing algorithms** + +**Estimated:** 40-60 hours + +--- + +### Future/Optional: +- Multi-user collaboration (complex, separate project) +- Cloud storage integration +- Mobile responsive design +- Version control UI (Git integration) +- Analytics dashboard + +--- + +## Bottom Line: Gaps Addressed + +**Critical Gaps** (must be added to plan): +1. āœ… **Undo/Redo** - Absolutely essential, add to Phase 1 +2. āœ… **Copy/Paste** - Users will expect this, add to Phase 2 +3. āœ… **Auto-Save** - Prevent data loss, add to Phase 1 +4. āœ… **Error Handling** - Graceful failures, add to Phase 1 + +**Important Gaps** (should be added): +5. āœ… **Keyboard Shortcuts** - Power user efficiency, Phase 2 +6. āœ… **Performance Strategy** - Test early, optimize if needed +7. āœ… **Circular Dependencies** - Validation requirement, Phase 2 +8. āœ… **Search/Filter** - Usability for large flows, Phase 2 + +**Nice-to-Have** (can be deferred): +9. āœ… **Comments** - Collaboration aid, Phase 4 +10. āœ… **Grouping** - Visual organization, Phase 4 +11. āœ… **Diff View** - Advanced feature, Phase 4 +12. āœ… **Accessibility** - Important but can be iterative +13. āœ… **Export to Image** - Documentation aid, Phase 4 + +**Out of Scope for Now:** +14. āœ… **Multi-User Collaboration** - Too complex for initial versions +15. āœ… **Mobile App** - Desktop-first approach recommended + +All critical and important gaps are now documented in this comprehensive planning document! + +--- + +## Conclusion + +### Feasibility: **Highly Feasible** +This visual editor is definitely possible and would significantly improve the maintainability of complex navigation flows. + +### Complexity: **Medium-High** +- Core functionality (nodes, connections, config panel): Medium complexity +- Advanced features (grouping, validation, testing): Higher complexity +- Total project could be completed in 3-4 weeks of focused development + +### Value Proposition: **Very High** +- Dramatically reduces time to create/edit navigation flows +- Visual representation makes logic much clearer +- Reduces errors from manual JSON editing +- Lowers barrier to entry for non-technical users +- Makes complex branching logic manageable + +### Recommended Next Steps +1. **Prototype:** Build a simple proof-of-concept with React Flow to validate the approach +2. **Feedback:** Show prototype to potential users for feedback +3. **Iterate:** Refine the UI/UX based on feedback +4. **MVP:** Build Phase 1 & 2 (core canvas + basic editing) +5. **Evaluate:** Assess if it's worth continuing to advanced features +6. **Complete:** Finish remaining phases based on needs + +### Alternative: Lower-Effort Options +If a full visual editor is too much effort: +1. **JSON Schema Editor:** Use existing JSON form builders with validation +2. **Spreadsheet Import:** Design navigation in Excel/Google Sheets, import to JSON +3. **Template Generator:** Web form to fill in, generates navigation.json +4. **Better Documentation:** Improve JSON comments and examples for manual editing + +This visual editor would transform navigation.json management from a manual, error-prone process into an intuitive, visual workflow—similar to how Talend transformed data integration pipelines. \ No newline at end of file diff --git a/planning/project_features_summary.md b/planning/project_features_summary.md new file mode 100644 index 0000000..f95ea54 --- /dev/null +++ b/planning/project_features_summary.md @@ -0,0 +1,208 @@ +# Product Finder - Project Features Summary + +**Date:** March 25, 2026 +**Project:** Product Finder Application for Columbia Metals +**Participants:** Jesse Salmon, Jason Soltys, Darlene Lampe + +--- + +## Core Application Features + +### 1. Product Selection Workflow +- **Multi-step navigation system** using button-based interface +- **Goal:** Minimize text entry - "tap, tap, tap" workflow +- **JSON-driven configuration** for flexible navigation without code changes +- Sequential selection process: Color → Size → Model/Type → Accessories + +### 2. Color Selection +- **Visual color buttons** (similar to current implementation showing Black, Bronze, Mill, etc.) +- Filters available products based on color selection +- **Mill finish to be removed** (not available for any door) +- Default hardware colors match door colors (e.g., bronze door = black pull, white door = white pull) + +### 3. Size Selection System + +#### Standard Sizes +- **Three standard sizes for storm doors:** + - 2668 (26" x 68") + - 2868 (28" x 68") + - 3068 (36" x 81") +- Display both nominal size (e.g., "3068") and actual dimensions (e.g., "36 x 81") +- **Button-based interface** (similar to color selection) +- Standard sizes auto-populate width and height fields (grayed out/non-editable) + +#### Custom Sizes +- **Custom option button** reveals width/height input fields +- **Storm doors:** Opening size ONLY (no tip-to-tip) + - Display warning in RED: "Opening size only - not tip to tip" +- **Patio doors:** Either opening size or tip-to-tip allowed +- Toggle between standard size buttons and custom input + +### 4. Product Categories + +#### Storm Doors +- **Models:** Cobra, King, King Dual Vent +- Category filtering based on selected criteria +- Product images for each model +- **Measurement requirement:** Opening size only for custom orders + +#### Patio Doors +- Standard size: 5069 +- Can use opening size or tip-to-tip for custom + +#### Products to Exclude +- Parts (Z-bars, inserts) +- Discontinued items (marked in database) +- M1200 patio storm door +- Storm door inserts + +### 5. Hardware & Accessories + +#### Hardware Selection +- **Types:** Brass lever, Satin silver, Black pull, White pull +- Labeled as "Hardware" in CGW system +- Default hardware based on door color +- Optional customization via dropdown + +#### Hinge Side Selection +- **Required field** - no default setting +- Options: Left hand or Right hand +- **Measurement instruction:** "Facing the door from the outside, which side are the hinges on?" +- Visual prompt/popup to ensure customer understands measurement perspective + +### 6. Visual Elements + +#### Product Images +- Display product photos for each door model +- **Door image flipping:** Mirror image based on hinge side selection (JavaScript/CSS) +- Images sourced from website and database + +#### Outside View Indicator +- **Proposed design:** Doghouse illustration with: + - Small roof/overhang + - Plant or bush beside door + - Wall indication +- Purpose: Help customers understand they're viewing from outside +- Simple, rough design acceptable + +#### Navigation Elements +- Button-based interface throughout +- Visual color swatches +- Product thumbnails +- Continue/Next buttons + +### 7. Data Management + +#### Database Integration +- **Source:** CGW (Columbia Gateway Windows) database +- **Locations:** Iola, Kansas City, Lindsberg +- Google Sheets for product data management +- Product codes exported from CGW + +#### Data Filtering +- Filter discontinued items (marked in spreadsheet) +- Remove accessories from main product list +- **Column H (Door Type/Subtype):** Delete to remove from product list +- **Columns J & K:** Accessory/hardware flags + +#### Product Naming Consistency +- Challenge: Same products have different model numbers across locations +- **Solution:** Use catalog names (customer-facing) +- Internal CGW names vary by plant +- Need standardization project for matching products across locations + +### 8. User Experience Features + +#### Add to Cart/Order +- Button to add selected product to sales order +- Appears after all selections made +- Label: "Add to [cart/sales order/hardware]" + +#### Measurement Instructions +- **Link to PDF** with measurement guidelines +- Accessible from size selection screen +- Shows how to determine hinge side, measure openings, etc. + +#### Validation & Warnings +- Opening size requirement for custom storm doors (highlighted in red) +- Required field indicators (hinge side selection) +- Visual cues for defaults vs. custom selections + +### 9. Architecture & Technical Approach + +#### Navigation System +- JSON file manages all navigation flow +- Changes made via JSON manipulation (no code changes needed) +- Flexible architecture to support iteration +- **Current limitation:** Toggle functionality requires code changes + +#### Development Philosophy +- Hard-code initial features for rapid iteration +- Avoid premature optimization +- Build architecture after understanding full requirements +- Keep front-end decoupled from back-end structure + +### 10. Future Considerations + +#### Windows +- Primary windows and storm windows have different measurement rules +- "Whole different ball game" - to be addressed later + +#### Parts/Accessories Integration +- Parts sheet for storm door inserts +- Separate handling from complete products +- Discontinued items may need parts availability + +#### Multi-Location Support +- Consolidate product naming across plants +- Maintain location-specific inventory +- Unified catalog names for customers + +--- + +## Key Business Rules + +1. **Storm doors (custom):** Opening size only +2. **Patio doors (custom):** Opening size OR tip-to-tip +3. **Standard sizes:** Auto-populate dimensions (read-only) +4. **Hinge side:** Required selection with instructional prompt +5. **Hardware:** Defaults based on color, optional customization +6. **Discontinued items:** Filter from product display +7. **Parts/accessories:** Exclude from main product categories + +--- + +## Implementation Priorities + +### High Priority +1. Size selection with standard sizes as buttons +2. Remove discontinued items from display +3. Hinge side selection with flip image functionality +4. Hardware dropdown with defaults +5. Custom size with opening-size-only warning for storm doors + +### Medium Priority +1. Outside view visual indicator (doghouse design) +2. Link to measurement instructions PDF +3. Add to cart/order button +4. Product naming standardization across locations + +### Low Priority (Future) +1. Windows integration +2. Parts/accessories handling +3. Advanced filtering options +4. Multi-location inventory management + +--- + +## Quality & Accuracy Concerns + +- **Arnie's primary concern:** Accuracy of the system +- Emphasis on preventing measurement errors +- Visual aids to reduce confusion +- Clear labeling and validation messages +- "It's too complicated" - need to prove simplicity through good UX + +--- + +*This summary was generated from the March 25, 2026 project planning meeting transcript.* diff --git a/planning/talk.docx b/planning/talk.docx new file mode 100644 index 0000000..e7985db Binary files /dev/null and b/planning/talk.docx differ diff --git a/product_app/README.md b/product_app/README.md new file mode 100644 index 0000000..3793c65 --- /dev/null +++ b/product_app/README.md @@ -0,0 +1,147 @@ +# Product App - Modular Flask Application + +## šŸ—ļø Structure + +``` +product_app/ +ā”œā”€ā”€ app.py # Main application file +ā”œā”€ā”€ passenger_wsgi.py # Passenger WSGI entry point for production +ā”œā”€ā”€ requirements.txt # Python dependencies +ā”œā”€ā”€ blueprints/ # Modular routes organized by feature +│ ā”œā”€ā”€ __init__.py +│ ā”œā”€ā”€ auth.py # Login/logout/session management +│ └── users.py # User CRUD operations +ā”œā”€ā”€ templates/ # HTML templates +│ ā”œā”€ā”€ login.html +│ └── user_manager.html +└── data/ # Data storage + └── users.json # User accounts with hashed passwords +``` + +## šŸŽÆ Features Implemented + +### āœ… Authentication System (blueprints/auth.py) +- Login with username/password +- Session management +- Password hashing (PBKDF2-SHA256, 1M iterations) +- Login required decorator +- Permission checking: `can_user('manage_users')` +- User status checking (active/inactive) + +### āœ… User Management (blueprints/users.py) +- View all users +- Add new users +- Delete users +- Toggle active/inactive status +- Change passwords (requires admin verification) +- Location-based access control +- Permission system (manage_users, create_quotes, etc.) +- Download users as JSON + +## šŸ” Default Login + +- **Username:** `Master` +- **Password:** `Master` +- **Permissions:** Full access (manage_users) + +## šŸš€ Local Testing + +```bash +cd product_app +python app.py +``` + +Visit: http://localhost:8080/ + +## Routes + +### Main Routes +- `/` - Home (redirects to login if not authenticated) +- `/test` - Test page to verify app is running + +### Authentication Routes (auth_bp) +- `/login` - Login page +- `/logout` - Logout +- `/api/login` - POST: Login API +- `/api/session` - GET: Current session info + +### User Management Routes (users_bp) +- `/users/` - User management page +- `/users/api` - GET: List all users, POST: Add user +- `/users/api/` - DELETE: Delete user +- `/users/api//active` - PATCH: Toggle active status +- `/users/api//change-password` - POST: Change password +- `/users/api/download` - GET: Download users.json + +## šŸ“¦ Production Deployment + +### Upload to Server: `/home/bmdwtjuw/product-finder/` + +Files to upload: +- `app.py` +- `passenger_wsgi.py` (or just use existing) +- `blueprints/` (entire folder) + - `__init__.py` + - `auth.py` + - `users.py` +- `templates/` (entire folder) + - `login.html` + - `user_manager.html` +- `data/users.json` + +### Control Panel Settings + +- **Application startup file:** `passenger_wsgi.py` (or `app.py`) +- **Application Entry point:** `application` +- **Python version:** 3.13.11 + +### Test After Deployment: +1. https://columbiawindows.com/product-finder/test +2. https://columbiawindows.com/product-finder/login +3. Login with Master/Master +4. Test user management + +## šŸ”§ Adding New Features + +### Create a New Blueprint + +1. Create `blueprints/your_feature.py`: + +```python +from flask import Blueprint, render_template +from blueprints.auth import login_required, can_user + +your_feature_bp = Blueprint('your_feature', __name__, url_prefix='/your-feature') + +@your_feature_bp.route('/') +@login_required +def index(): + return render_template('your_feature.html') +``` + +2. Register in `app.py`: + +```python +from blueprints.your_feature import your_feature_bp +app.register_blueprint(your_feature_bp) +``` + +3. Create `templates/your_feature.html` + +4. Test locally, then upload to server + +## šŸ“ Benefits of Blueprint Structure + +āœ… **Separation of Concerns:** Each feature in its own file +āœ… **Easy to Maintain:** Find and edit specific features quickly +āœ… **Scalable:** Add new features without touching existing code +āœ… **Testable:** Each blueprint can be tested independently +āœ… **Reusable:** Share decorators (login_required, permission_required) across blueprints + +## šŸ› ļø Next Steps + +- Add more blueprints for other features (quotes, products, etc.) +- Add more templates as needed +- Extend permission system +- Add location selection page +- Add the main product finder quiz diff --git a/product_app/app.py b/product_app/app.py new file mode 100644 index 0000000..f3cd72c --- /dev/null +++ b/product_app/app.py @@ -0,0 +1,87 @@ +from flask import Flask, session, redirect, url_for +import secrets +import os + +# Get the directory where this script is located +basedir = os.path.abspath(os.path.dirname(__file__)) + +# Initialize Flask with explicit paths +app = Flask(__name__, + template_folder=os.path.join(basedir, 'templates'), + static_folder=os.path.join(basedir, 'static')) + +# Configure session - generate a random secret key +app.secret_key = secrets.token_hex(32) +app.config['SESSION_COOKIE_HTTPONLY'] = True +app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' + +# Import and register blueprints +from blueprints.auth import auth_bp +from blueprints.users import users_bp + +app.register_blueprint(auth_bp) +app.register_blueprint(users_bp) + +# Home route +@app.route('/') +def home(): + """Redirect to login if not authenticated, otherwise show home""" + if 'user_id' not in session: + return redirect(url_for('auth.login_page')) + + # If user hasn't selected a location yet, redirect to selection + if not session.get('currentLocation') and len(session.get('accessibleLocations', [])) > 1: + return redirect(url_for('auth.select_location_page')) + + return f""" + + Home + +

Welcome, {session.get('username', 'User')}!

+

Current Location: {session.get('currentLocation', 'Not selected')}

+ + + + """ + +@app.route('/test') +def test(): + """Test route to verify app is running""" + import sys + return f""" + + Test Page + +

Flask App is Running!

+
    +
  • Python: {sys.version}
  • +
  • Routes: {len(list(app.url_map.iter_rules()))}
  • +
+

Login

+ + + """ + +# Error handlers +@app.errorhandler(404) +def page_not_found(e): + return "

404 - Page Not Found

", 404 + +@app.errorhandler(500) +def internal_error(e): + return "

500 - Internal Server Error

", 500 + +# This is the critical line for Passenger +application = app + +if __name__ == '__main__': + # Create necessary directories + os.makedirs(os.path.join(basedir, 'templates'), exist_ok=True) + os.makedirs(os.path.join(basedir, 'data'), exist_ok=True) + + # Run the application + app.run(debug=True, host='0.0.0.0', port=8080) \ No newline at end of file diff --git a/product_app/blueprints/__init__.py b/product_app/blueprints/__init__.py new file mode 100644 index 0000000..81739f8 --- /dev/null +++ b/product_app/blueprints/__init__.py @@ -0,0 +1 @@ +# Blueprint package diff --git a/product_app/blueprints/auth.py b/product_app/blueprints/auth.py new file mode 100644 index 0000000..db378d8 --- /dev/null +++ b/product_app/blueprints/auth.py @@ -0,0 +1,253 @@ +""" +Authentication Blueprint +Handles login, logout, and session management +""" +from flask import Blueprint, render_template, request, jsonify, session, redirect, url_for +from werkzeug.security import check_password_hash +from functools import wraps +import json +import os + +auth_bp = Blueprint('auth', __name__) + +# Path to users file +USERS_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'users.json') + +def load_users(): + """Load users from JSON file""" + if os.path.exists(USERS_FILE): + try: + with open(USERS_FILE, 'r') as f: + return json.load(f) + except: + return [] + return [] + +def login_required(f): + """Decorator to require login for routes""" + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + return redirect(url_for('auth.login_page')) + + # Check if user is active + users = load_users() + if session['user_id'] >= len(users): + session.clear() + return redirect(url_for('auth.login_page', message='User not found')) + + user = users[session['user_id']] + if not user.get('active', True): + session.clear() + return redirect(url_for('auth.login_page', message='Account is inactive')) + + return f(*args, **kwargs) + return decorated_function + +def get_current_user(): + """Get the currently logged in user""" + if 'user_id' not in session: + return None + + users = load_users() + if session['user_id'] >= len(users): + return None + + return users[session['user_id']] + +def can_user(permission, location=None): + """ + Check if current user has a specific permission. + + Args: + permission (str): Permission name (e.g., 'manage_users') + location (str, optional): Check permission for specific location + + Returns: + bool: True if user has permission + """ + user = get_current_user() + if not user: + return False + + if not user.get('active', True): + return False + + # Check global permissions + global_permissions = user.get('permissions', {}) + if permission in global_permissions: + return global_permissions[permission] is True + + if location == 'global': + return False + + # Check location-specific permissions + check_location = location if location else session.get('currentLocation') + if not check_location: + return False + + location_settings = user.get('locationSettings', {}) + if check_location in location_settings: + loc_permissions = location_settings[check_location].get('permissions', {}) + if permission in loc_permissions: + return loc_permissions[permission] is True + + return False + +# Routes +@auth_bp.route('/login') +def login_page(): + """Serve the login page""" + if 'user_id' in session: + return redirect(url_for('home')) + return render_template('login.html') + +@auth_bp.route('/select-location') +def select_location_page(): + """Serve the location selection page""" + if 'user_id' not in session: + return redirect(url_for('auth.login_page')) + return render_template('select_location.html') + +@auth_bp.route('/api/login', methods=['POST']) +def login(): + """Authenticate user and create session""" + try: + data = request.json + username = data.get('username') + password = data.get('password') + + if not username or not password: + return jsonify({ + 'status': 'error', + 'message': 'Username and password are required' + }), 400 + + users = load_users() + + # Find user by username + user_index = None + user = None + for i, u in enumerate(users): + if u['username'] == username: + user_index = i + user = u + break + + if not user: + return jsonify({ + 'status': 'error', + 'message': 'Invalid username or password' + }), 401 + + # Check if user is active + if not user.get('active', True): + return jsonify({ + 'status': 'error', + 'message': 'Account is inactive. Please contact an administrator.' + }), 403 + + # Verify password + if not check_password_hash(user['password'], password): + return jsonify({ + 'status': 'error', + 'message': 'Invalid username or password' + }), 401 + + # Create session + session['user_id'] = user_index + session['username'] = user['username'] + session['defaultLocation'] = user.get('defaultLocation') + + # Get accessible locations + accessible_locations = [] + if 'locationSettings' in user: + for loc_code, settings in user['locationSettings'].items(): + if settings.get('accessible', False): + accessible_locations.append(loc_code) + + session['accessibleLocations'] = accessible_locations + + # Check if user needs to select a location + requiresLocationSelection = len(accessible_locations) > 1 + + # If only one location or no accessible locations, auto-select default + if not requiresLocationSelection: + session['currentLocation'] = user.get('defaultLocation') + + return jsonify({ + 'status': 'success', + 'message': 'Login successful', + 'requiresLocationSelection': requiresLocationSelection + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@auth_bp.route('/api/select-location', methods=['POST']) +def select_location(): + """Select a location for the current session""" + if 'user_id' not in session: + return jsonify({ + 'status': 'error', + 'message': 'Not logged in' + }), 401 + + try: + data = request.json + location = data.get('location') + + # Verify user has access to this location + accessible = session.get('accessibleLocations', []) + if location not in accessible: + return jsonify({ + 'status': 'error', + 'message': 'You do not have access to this location' + }), 403 + + session['currentLocation'] = location + + return jsonify({ + 'status': 'success', + 'message': 'Location selected', + 'currentLocation': location + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@auth_bp.route('/logout') +def logout(): + """Log out user and clear session""" + session.clear() + return redirect(url_for('auth.login_page')) + +@auth_bp.route('/api/session', methods=['GET']) +def get_session(): + """Get current session information""" + if 'user_id' not in session: + return jsonify({ + 'status': 'error', + 'message': 'Not logged in' + }), 401 + + # Get user permissions + user = get_current_user() + permissions = {} + if user: + permissions = user.get('permissions', {}) + + return jsonify({ + 'status': 'success', + 'username': session.get('username'), + 'defaultLocation': session.get('defaultLocation'), + 'currentLocation': session.get('currentLocation'), + 'accessibleLocations': session.get('accessibleLocations', []), + 'permissions': permissions + }) diff --git a/product_app/blueprints/users.py b/product_app/blueprints/users.py new file mode 100644 index 0000000..fd6b018 --- /dev/null +++ b/product_app/blueprints/users.py @@ -0,0 +1,276 @@ +""" +User Management Blueprint +Handles user CRUD operations +""" +from flask import Blueprint, render_template, request, jsonify, send_file +from werkzeug.security import generate_password_hash, check_password_hash +from blueprints.auth import login_required, can_user, get_current_user +from io import BytesIO +import json +import os + +users_bp = Blueprint('users', __name__, url_prefix='/users') + +# Path to users file +USERS_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'users.json') + +def load_users(): + """Load users from JSON file""" + if os.path.exists(USERS_FILE): + try: + with open(USERS_FILE, 'r') as f: + return json.load(f) + except: + return [] + return [] + +def save_users(users): + """Save users to JSON file""" + os.makedirs(os.path.dirname(USERS_FILE), exist_ok=True) + with open(USERS_FILE, 'w') as f: + json.dump(users, f, indent=2) + +def permission_required(permission): + """Decorator to require specific permission""" + from functools import wraps + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if not can_user(permission): + return jsonify({ + 'status': 'error', + 'message': 'Permission denied' + }), 403 + return f(*args, **kwargs) + return decorated_function + return decorator + +# Routes +@users_bp.route('/') +@login_required +def user_manager(): + """Serve the user management page""" + if not can_user('manage_users'): + return "

403 - Permission Denied

You don't have permission to manage users.

", 403 + return render_template('user_manager.html') + +@users_bp.route('/api', methods=['GET']) +@login_required +@permission_required('manage_users') +def get_users(): + """Get all users""" + users = load_users() + return jsonify({ + 'status': 'success', + 'users': users + }) + +@users_bp.route('/api', methods=['POST']) +@login_required +@permission_required('manage_users') +def add_user(): + """Add a new user with hashed password""" + try: + data = request.json + + # Validate required fields + if not data.get('username') or not data.get('password') or not data.get('defaultLocation'): + return jsonify({ + 'status': 'error', + 'message': 'Username, password, and default location are required' + }), 400 + + users = load_users() + + # Check if username already exists + if any(user['username'] == data['username'] for user in users): + return jsonify({ + 'status': 'error', + 'message': 'Username already exists' + }), 400 + + # Hash the password + hashed_password = generate_password_hash(data['password'], method='pbkdf2:sha256') + + # Get location settings + location_settings = data.get('locationSettings', {}) + default_location = data['defaultLocation'] + + # Ensure default location is accessible + if default_location not in location_settings: + location_settings[default_location] = {} + location_settings[default_location]['accessible'] = True + + # Create new user + new_user = { + 'username': data['username'], + 'password': hashed_password, + 'defaultLocation': default_location, + 'locationSettings': location_settings, + 'permissions': data.get('permissions', {}), + 'active': data.get('active', True) + } + + users.append(new_user) + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': 'User added successfully', + 'users': users + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@users_bp.route('/api/', methods=['DELETE']) +@login_required +@permission_required('manage_users') +def delete_user(user_index): + """Delete a specific user by index""" + try: + users = load_users() + + if user_index < 0 or user_index >= len(users): + return jsonify({ + 'status': 'error', + 'message': 'Invalid user index' + }), 400 + + users.pop(user_index) + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': 'User deleted successfully', + 'users': users + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@users_bp.route('/api//active', methods=['PATCH']) +@login_required +@permission_required('manage_users') +def toggle_user_active(user_index): + """Toggle user active status""" + try: + data = request.json + users = load_users() + + if user_index < 0 or user_index >= len(users): + return jsonify({ + 'status': 'error', + 'message': 'Invalid user index' + }), 400 + + users[user_index]['active'] = data.get('active', True) + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': 'User status updated', + 'users': users + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@users_bp.route('/api//change-password', methods=['POST']) +@login_required +@permission_required('manage_users') +def change_user_password(user_index): + """Change a user's password with current user verification""" + try: + data = request.json + new_password = data.get('newPassword') + current_user_password = data.get('currentUserPassword') + + if not new_password or not current_user_password: + return jsonify({ + 'status': 'error', + 'message': 'New password and current user password are required' + }), 400 + + if len(new_password) < 6: + return jsonify({ + 'status': 'error', + 'message': 'Password must be at least 6 characters long' + }), 400 + + # Verify current user's password + current_user = get_current_user() + if not current_user: + return jsonify({ + 'status': 'error', + 'message': 'Not authenticated' + }), 401 + + if not check_password_hash(current_user['password'], current_user_password): + return jsonify({ + 'status': 'error', + 'message': 'Your password is incorrect. Password change denied for security reasons.' + }), 403 + + # Load users and validate index + users = load_users() + if user_index < 0 or user_index >= len(users): + return jsonify({ + 'status': 'error', + 'message': 'Invalid user index' + }), 400 + + # Update password + hashed_password = generate_password_hash(new_password, method='pbkdf2:sha256') + target_username = users[user_index]['username'] + users[user_index]['password'] = hashed_password + save_users(users) + + return jsonify({ + 'status': 'success', + 'message': f'Password changed successfully for user "{target_username}"' + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@users_bp.route('/api/download') +@login_required +@permission_required('manage_users') +def download_users(): + """Download users as JSON file""" + try: + users = load_users() + + if not users: + return jsonify({ + 'status': 'error', + 'message': 'No users to download' + }), 400 + + json_data = json.dumps(users, indent=2) + + return send_file( + BytesIO(json_data.encode()), + mimetype='application/json', + as_attachment=True, + download_name='users.json' + ) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 diff --git a/product_app/data/users.json b/product_app/data/users.json new file mode 100644 index 0000000..649a86e --- /dev/null +++ b/product_app/data/users.json @@ -0,0 +1,25 @@ +[ + { + "username": "Master", + "password": "pbkdf2:sha256:1000000$tPRmseRGKLveXTxS$87eda9a20db29d12838fcb607f1f3536141f743ec9879702dc2a0dd893b4078d", + "defaultLocation": "KC", + "permissions": { + "manage_users": true + }, + "locationSettings": { + "LINDS": { + "accessible": true + }, + "IOLA": { + "accessible": true + }, + "KC": { + "accessible": true + }, + "BMD": { + "accessible": true + } + }, + "active": true + } +] diff --git a/product_app/passenger_wsgi.py b/product_app/passenger_wsgi.py new file mode 100644 index 0000000..d221b58 --- /dev/null +++ b/product_app/passenger_wsgi.py @@ -0,0 +1,17 @@ +import importlib.machinery +import importlib.util +import os +import sys + + +sys.path.insert(0, os.path.dirname(__file__)) + +def load_source(modname, filename): + loader = importlib.machinery.SourceFileLoader(modname, filename) + spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + +wsgi = load_source('wsgi', 'app.py') +application = wsgi.application diff --git a/product_app/requirements.txt b/product_app/requirements.txt new file mode 100644 index 0000000..02ae215 --- /dev/null +++ b/product_app/requirements.txt @@ -0,0 +1 @@ +Flask==3.1.3 diff --git a/product_app/templates/login.html b/product_app/templates/login.html new file mode 100644 index 0000000..9e34433 --- /dev/null +++ b/product_app/templates/login.html @@ -0,0 +1,213 @@ + + + + + + Login + + + + + + + + diff --git a/product_app/templates/select_location.html b/product_app/templates/select_location.html new file mode 100644 index 0000000..e21f02e --- /dev/null +++ b/product_app/templates/select_location.html @@ -0,0 +1,375 @@ + + + + + + Select Location + + + +
+
+

šŸ“ Select Your Location

+

Choose which location you'll be working from today

+
+ + + +
+ +
+ +
+ + + + +
+ + + + diff --git a/product_app/templates/user_manager.html b/product_app/templates/user_manager.html new file mode 100644 index 0000000..957a6e1 --- /dev/null +++ b/product_app/templates/user_manager.html @@ -0,0 +1,498 @@ + + + + + + User Manager + + + +
+
+

šŸ‘„ User Manager

+ ← Back to Home +
+ +
+
+ + +
+

Add New User

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+

Existing Users

+
+
+
+ + + + diff --git a/teacher_retention_trend.svg b/teacher_retention_trend.svg new file mode 100644 index 0000000..ec32637 --- /dev/null +++ b/teacher_retention_trend.svg @@ -0,0 +1,1356 @@ + + + + + + + + 2026-03-25T09:27:40.978128 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wordpress_.htaccess b/wordpress_.htaccess new file mode 100644 index 0000000..9e1df3c --- /dev/null +++ b/wordpress_.htaccess @@ -0,0 +1,93 @@ +# BEGIN Force HTTPS + +RewriteEngine On + +# Skip ACME challenge +RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/ + +# Redirect HTTP to HTTPS +RewriteCond %{HTTPS} off +RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + +# END Force HTTPS + +# BEGIN LSCACHE +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## + +RewriteEngine on +RewriteRule litespeed/debug/.*\.log$ - [F,L] +RewriteRule \.litespeed_conf\.dat - [F,L] + + +CacheLookup on +RewriteRule .* - [E=Cache-Control:no-autoflush] + +### marker ASYNC start ### +RewriteCond %{REQUEST_URI} /wp-admin/admin-ajax\.php +RewriteCond %{QUERY_STRING} action=async_litespeed +RewriteRule .* - [E=noabort:1] +### marker ASYNC end ### + +### marker DROPQS start ### +CacheKeyModify -qs:fbclid +CacheKeyModify -qs:gclid +CacheKeyModify -qs:utm* +CacheKeyModify -qs:_ga +### marker DROPQS end ### + + +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## +# END LSCACHE +# BEGIN NON_LSCACHE +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## +## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ## +# END NON_LSCACHE + + +# BEGIN Security Block +# Block the include-only files. + +RewriteEngine On +RewriteBase / +RewriteRule ^wp-admin/includes/ - [F,L] +RewriteRule !^wp-includes/ - [S=3] +RewriteRule ^wp-includes/[^/]+\.php$ - [F,L] +RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L] +RewriteRule ^wp-includes/theme-compat/ - [F,L] + + +# Disable directory listing +Options All -Indexes + +# Remove header with PHP version +Header always unset X-Powered-By +Header unset X-Powered-By +# END Security Block + +# BEGIN LiteSpeed +# The directives (lines) between "BEGIN LiteSpeed" and "END LiteSpeed" are +# dynamically generated, and should only be modified via WordPress filters. +# Any changes to the directives between these markers will be overwritten. + +SetEnv noabort 1 + +# END LiteSpeed +# BEGIN WordPress +# The directives (lines) between "BEGIN WordPress" and "END WordPress" are +# dynamically generated, and should only be modified via WordPress filters. +# Any changes to the directives between these markers will be overwritten. + +RewriteEngine On +RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] +RewriteBase / +RewriteRule ^index\.php$ - [L] + +# *** ADD THIS LINE - Exclude product-finder from WordPress routing *** +RewriteCond %{REQUEST_URI} !^/product-finder/ + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . /index.php [L] + + +# END WordPress