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