Initial
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user