Initial
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.*
|
||||
Binary file not shown.
Reference in New Issue
Block a user