Changed application path from /hm-ems/ to /hm-ems-report/ to match production URL structure. Updated all references to use HTTPS and correct domain. Changes: - Path prefix: /hm-ems/ → /hm-ems-report/ - Production URL: https://ai-sandbox.oliver.solutions/hm-ems-report/ - Created apache/hm-ems-report.conf with full Apache configuration - Updated all API and image paths in frontend and backend - Updated documentation with correct URLs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.1 KiB
6.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
H&M EMS Product Review Tool - A web-based application for reviewing and editing H&M product data across multiple languages. The tool allows clients to review product names and prices, make inline edits, approve changes, and save them back to master JSON files with full change logging.
Architecture
Two Operation Modes
- Server Mode (
server.py): Live Flask web app with API endpoints for real-time editing, approval, and saving back to master JSON - Static Generator Mode (
html_generator.py): Generates self-contained HTML files with embedded data (can be run standalone)
Core Components
-
server.py: Flask backend serving API endpoints and static files
- Thread-safe JSON read/write operations using
file_lock - Change logging to
*_changelog.jsonfiles alongside master JSON - Image serving with path traversal protection
- Thread-safe JSON read/write operations using
-
html_generator.py: Shared utilities and standalone HTML generator
LANGUAGE_DISPLAY_NAMES: 60+ language/locale display mappings- Helper functions for campaign prefix extraction and image filename processing
- When run standalone, generates fully self-contained HTML with embedded JS/CSS
-
static/index.html: Client-side review UI
- Dynamic column management (up to 4 target languages side-by-side)
- Inline editing with real-time change tracking
- Approve/unapprove workflow with field locking
- Multi-image support with click-to-enlarge
Data Flow
Master_Json/{campaign}.json
↓ (loaded by server or generator)
Campaign data grouped by Article ID
↓ (processed with image mapping)
Frontend table with editable fields
↓ (on approve)
POST /api/approve → updates master JSON + appends changelog
Image Handling
- Source:
.tiffiles listed in JSONFilenamefield (comma-separated for multi-image articles) - Conversion:
.tif→.jpg(handled by image path helpers) - Location:
campaign_images/{year}/{campaign}/Automation_LR/ - Year derivation: First 4 digits of
Seasonfield (e.g., "202502" → "2025") - Campaign prefix: Text before first underscore in JSON filename (e.g., "1022A.json" → "1022A")
Approval & Changelog System
- Approval state: Tracked per Article ID + Language combination
- Changelog format:
{campaign}_changelog.jsonstores timestamped entries with:- Article ID, Language, Country, Product ID
- Action: "approve" or "unapprove"
- Edits array: field name, old value, new value
- Approval persistence: On load, changelog is replayed to rebuild approval state
- Thread safety: All JSON writes use
file_lockto prevent concurrent modification
Development Commands
Local Development Setup
# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Run locally
python3 server.py
# Opens on http://localhost:5000
Production Deployment
Application runs on Ubuntu with Apache reverse proxy. See DEPLOY.md for full instructions.
Quick deploy:
cd /opt/hm_ems_report
sudo ./deploy.sh
Architecture:
- Flask/Gunicorn runs as systemd service on port 5000 (localhost only)
- Apache serves static files from
/var/www/html/hm-ems-report/ - Apache proxies
/hm-ems-report/api/*to Flask backend - Apache serves images directly from
/opt/hm-ems-data/campaign_images/ - Data persisted in
/opt/hm-ems-data/(JSON files + images)
Production URL:
- Web UI:
https://ai-sandbox.oliver.solutions/hm-ems-report/ - API:
https://ai-sandbox.oliver.solutions/hm-ems-report/api/* - Images:
https://ai-sandbox.oliver.solutions/hm-ems-report/images/*
Run with Gunicorn (production)
gunicorn server:app --bind 0.0.0.0:5000 --workers 4
Generate standalone HTML site
python html_generator.py <json_file> [output_dir] [image_base_path]
# Example:
python html_generator.py Master_Json/1022A.json ./html_output/1022A ./campaign_images
Configuration
All paths configurable via environment variables:
MASTER_JSON_DIR: Campaign JSON files location (default:./Master_Json)IMAGE_BASE_PATH: Root folder for{year}/{campaign}/Automation_LR/images (default:./campaign_images)PORT: Server port (default:5000)
Example:
IMAGE_BASE_PATH=/custom/images MASTER_JSON_DIR=/custom/json PORT=8080 python3 server.py
API Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| GET | / |
Serve review UI |
| GET | /api/files |
List available campaign JSON files (excludes *_changelog.json) |
| GET | /api/load/<filename> |
Load campaign data with image map, languages, approvals |
| POST | /api/approve |
Approve/unapprove record, apply edits to master JSON, log changes |
| GET | /images/<year>/<campaign>/<file> |
Serve campaign images (with path traversal protection) |
Adding New Campaigns
- Place JSON file in
Master_Json/(e.g.,3045A.json) - Place images in
campaign_images/{year}/{campaign}/Automation_LR/- Year: First 4 digits of
Seasonfield in JSON - Campaign: Text before first underscore in filename
- Year: First 4 digits of
- Refresh browser - new campaign appears in dropdown
Key Implementation Details
Security
- Path traversal protection: All file/image paths use
os.path.basename()before joining - Changelog files excluded from campaign list (filter
_changelog.json)
Frontend State Management
langColumnsarray: Dynamic language column configuration (up to 4)changesobject: Keyed by{articleId}::{lang}::{field}for tracking editsapprovalsobject: Keyed by{articleId}::{lang}for approval state- Trailing empty dropdown: Automatically added when columns < 4 and last is filled
Data Grouping
- Records grouped by Article ID (each article has multiple language variants)
- English (GB) always shown as reference column (non-editable)
- Product name field highlights when differs from English (case-insensitive comparison)
Concurrency
threading.Lock()ensures safe concurrent JSON read/write operations- Changelog append is atomic within the lock scope