- 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>
277 lines
8.8 KiB
Python
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)
|