""" 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()