This commit is contained in:
Jason
2026-04-11 00:04:09 -05:00
commit 7a2fffd62e
110 changed files with 51809 additions and 0 deletions
+451
View File
@@ -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<Object>} 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<Object>} 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: [] }
*/