ferrero-naming-tool/backend/app.py
DJP 82eff7b76a Initial commit: Ferrero Communication Assets Naming Convention Tool
- PHP-based naming convention tool for Ferrero communication assets
- Filename builder with validation
- Filename decoder with component breakdown
- Complete help documentation
- Ferrero brand styling with Montserrat font
- OMG Job Number field (numbers only, max 10 chars)
- 105 brands, 29 countries, 39 asset types from Excel
- Responsive landscape layout
- Data management via JSON (parsed from Excel)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 16:05:16 -04:00

277 lines
8.8 KiB
Python

#!/usr/bin/env python3
"""
Flask API for Ferrero Naming Convention Tool
"""
from flask import Flask, jsonify, request
from flask_cors import CORS
import json
import os
import re
app = Flask(__name__)
CORS(app)
# Load data
DATA_FILE = os.path.join(os.path.dirname(__file__), 'data.json')
def load_data():
"""Load naming convention data from JSON"""
with open(DATA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
def save_data(data):
"""Save naming convention data to JSON"""
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
@app.route('/api/data', methods=['GET'])
def get_data():
"""Get all naming convention data"""
try:
data = load_data()
return jsonify(data), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/brands', methods=['GET', 'POST'])
def brands():
"""Get all brands or add a new brand"""
data = load_data()
if request.method == 'GET':
return jsonify(data['brands']), 200
elif request.method == 'POST':
try:
new_brand = request.json
code = new_brand.get('code', '').upper()
name = new_brand.get('name', '')
if not code or not name:
return jsonify({'error': 'Code and name are required'}), 400
if len(code) < 2 or len(code) > 5:
return jsonify({'error': 'Brand code must be 2-5 characters'}), 400
data['brands'][code] = name
save_data(data)
return jsonify({'message': 'Brand added successfully', 'code': code, 'name': name}), 201
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/brands/<code>', methods=['DELETE'])
def delete_brand(code):
"""Delete a brand"""
data = load_data()
if code not in data['brands']:
return jsonify({'error': 'Brand not found'}), 404
del data['brands'][code]
save_data(data)
return jsonify({'message': 'Brand deleted successfully'}), 200
@app.route('/api/countries', methods=['GET', 'POST'])
def countries():
"""Get all countries or add a new country"""
data = load_data()
if request.method == 'GET':
return jsonify(data['countries']), 200
elif request.method == 'POST':
try:
new_country = request.json
code = new_country.get('code', '').upper()
name = new_country.get('name', '')
if not code or not name:
return jsonify({'error': 'Code and name are required'}), 400
if len(code) != 2:
return jsonify({'error': 'Country code must be exactly 2 characters'}), 400
data['countries'][code] = name
save_data(data)
return jsonify({'message': 'Country added successfully', 'code': code, 'name': name}), 201
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/asset-types', methods=['GET', 'POST'])
def asset_types():
"""Get all asset types or add a new asset type"""
data = load_data()
if request.method == 'GET':
return jsonify(data['asset_types']), 200
elif request.method == 'POST':
try:
new_type = request.json
code = new_type.get('code', '').upper()
name = new_type.get('name', '')
if not code or not name:
return jsonify({'error': 'Code and name are required'}), 400
if len(code) != 3:
return jsonify({'error': 'Asset type code must be exactly 3 characters'}), 400
data['asset_types'][code] = name
save_data(data)
return jsonify({'message': 'Asset type added successfully', 'code': code, 'name': name}), 201
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/build', methods=['POST'])
def build_filename():
"""Build a filename from components"""
try:
params = request.json
brand_code = params.get('brand_code', '').upper()
country_code = params.get('country_code', '').upper()
subject_title = params.get('subject_title', '').upper().replace(' ', ' ')
asset_type = params.get('asset_type', '').upper()
spot_version = params.get('spot_version', '').upper()
seconds = params.get('seconds', '')
aspect_ratio = params.get('aspect_ratio', '')
has_master = params.get('has_master', False)
# Validation
errors = []
if not brand_code or len(brand_code) < 2 or len(brand_code) > 5:
errors.append('Brand code must be 2-5 characters')
if not country_code or len(country_code) != 2:
errors.append('Country code must be exactly 2 characters')
if not subject_title or len(subject_title) > 15:
errors.append('Subject title is required and must be max 15 characters')
if not asset_type or len(asset_type) != 3:
errors.append('Asset type must be exactly 3 characters')
if not seconds:
errors.append('Duration in seconds is required')
if not aspect_ratio or len(aspect_ratio) != 4:
errors.append('Aspect ratio must be exactly 4 characters')
if errors:
return jsonify({'error': ', '.join(errors)}), 400
# Build filename
parts = [brand_code, country_code, subject_title, asset_type]
# Add spot version and MST if applicable
if has_master and spot_version:
parts.append(f"{spot_version}_MST")
elif spot_version:
parts.append(spot_version)
elif has_master:
parts.append("MST")
# Add duration
parts.append(f"{seconds}S")
# Add aspect ratio
parts.append(aspect_ratio)
filename = '_'.join(parts)
return jsonify({'filename': filename}), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/decode', methods=['POST'])
def decode_filename():
"""Decode a filename into its components"""
try:
filename = request.json.get('filename', '')
if not filename:
return jsonify({'error': 'Filename is required'}), 400
# Remove file extension if present
filename = filename.split('.')[0]
# Split by underscore
parts = filename.split('_')
if len(parts) < 6:
return jsonify({'error': 'Invalid filename format. Expected at least 6 parts separated by underscores'}), 400
# Parse components
result = {
'brand_code': parts[0],
'country_code': parts[1],
'subject_title': parts[2],
'asset_type': parts[3],
'spot_version': '',
'has_master': False,
'seconds': '',
'aspect_ratio': ''
}
# Handle optional spot version and MST
current_index = 4
# Check if there's a spot version and/or MST
while current_index < len(parts):
part = parts[current_index]
# Check if it's the duration (ends with S and has numbers)
if re.match(r'^\d+S$', part):
result['seconds'] = part[:-1] # Remove 'S'
current_index += 1
break
# Check if it's MST
elif part == 'MST':
result['has_master'] = True
current_index += 1
# Otherwise it's a spot version
else:
result['spot_version'] = part
current_index += 1
# Last part should be aspect ratio
if current_index < len(parts):
result['aspect_ratio'] = parts[current_index]
# Load data to get full names
data = load_data()
result['brand_name'] = data['brands'].get(result['brand_code'], 'Unknown Brand')
result['country_name'] = data['countries'].get(result['country_code'], 'Unknown Country')
result['asset_type_name'] = data['asset_types'].get(result['asset_type'], 'Unknown Asset Type')
return jsonify(result), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/validate', methods=['POST'])
def validate_filename():
"""Validate a filename against naming convention rules"""
try:
filename = request.json.get('filename', '')
if not filename:
return jsonify({'error': 'Filename is required'}), 400
# Try to decode it
decode_result = decode_filename()
if decode_result[1] == 200:
return jsonify({'valid': True, 'message': 'Filename is valid'}), 200
else:
return jsonify({'valid': False, 'message': 'Filename does not follow naming convention'}), 200
except Exception as e:
return jsonify({'valid': False, 'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5001)