---
name: "H&M EMS Product Review Tool"
client: H&M
status: active
server: ai-sandbox.oliver.solutions
tech: [Python, Flask, HTML5, JavaScript, JSON, Gunicorn, Apache]
local_path: /Users/ai_leed/Documents/Projects/Oliver/hm_ems_report
deploy: sudo ./deploy.sh
url: https://ai-sandbox.oliver.solutions/hm-ems-report
tags: [hm, ems, product-review, multilanguage, json]
created: 2026-04-14
port: 5000
service: hm-ems-report
---

## Overview
H&M EMS Product Review Tool is a web-based application for reviewing and editing H&M product data (names, prices) across multiple languages. Clients access a single-page UI to compare product information side-by-side in up to 4 target languages, make inline edits, approve changes, and automatically save them back to master JSON files with full change logging. It operates in two modes: as a live Flask server with API endpoints or as a static HTML generator for standalone use.

## Tech Stack
- **Frontend:** HTML5, vanilla JavaScript (single-page app, no framework)
- **Backend:** Python 3, Flask 2.0+
- **Database:** JSON files (flat file storage with changelog tracking)
- **Infrastructure:** Apache reverse proxy + Gunicorn (production); direct Flask (development)
- **AI/ML:** N/A
- **Key libraries:** Flask, Gunicorn, file-based locking for thread-safe JSON operations

## Architecture

### Two Operation Modes
1. **Server Mode** (`server.py`): Live Flask web app serving API endpoints and static files with real-time editing and approval
2. **Static Generator Mode** (`html_generator.py`): Generates self-contained HTML files with embedded data for standalone use

### Core Components
- **server.py**: Flask backend
  - Thread-safe JSON read/write using file locks
  - Change logging to `*_changelog.json` files
  - Image serving with path traversal protection
  - API endpoints for campaigns, approvals, and saves

- **html_generator.py**: Shared utilities (60+ language/locale mappings, image path helpers)

- **static/index.html**: Client-side SPA with:
  - Dynamic column management (up to 4 target languages)
  - 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 Flask 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:** `.tif` files listed in JSON `Filename` field (comma-separated for multi-image articles)
- **Conversion:** `.tif` → `.jpg` (via helper functions)
- **Location:** `campaign_images/{year}/{campaign}/Automation_LR/`
- **Year derivation:** First 4 digits of `Season` field (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.json` stores timestamped entries with Article ID, Language, Country, Product ID, action ("approve"/"unapprove"), and edits array
- **Approval persistence:** On load, changelog is replayed to rebuild approval state
- **Thread safety:** All JSON writes use `file_lock` to prevent concurrent modification

## Dev Commands
```bash
# Setup virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Run locally on http://localhost:5000
python3 server.py

# Run with Gunicorn (production-like)
gunicorn server:app --bind 0.0.0.0:5000 --workers 4
```

## Deployment
- **Server:** ai-sandbox.oliver.solutions (Ubuntu with Apache)
- **Deploy:** `sudo ./deploy.sh` from `/opt/hm_ems_report`
- **URL:** https://ai-sandbox.oliver.solutions/hm-ems-report
- **Port:** 5000 (internal Gunicorn); Apache reverse proxies at :443
- **Service:** `hm-ems-report` (systemd service)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/hm_ems_report

### Architecture
- **User Request (HTTPS)** → Apache (:443) on ai-sandbox.oliver.solutions
  - `/hm-ems-report/` (static files) → `/var/www/html/hm-ems-report/`
  - `/hm-ems-report/api/*` (proxy) → Gunicorn :5000 → Flask
  - `/hm-ems-report/images/*` → `/opt/hm-ems-data/campaign_images/`

### Setup (First Time)
1. Clone to `/opt/hm_ems_report`
2. Create data directories: `/opt/hm-ems-data/{Master_Json,campaign_images,backups}` and `/var/log/hm-ems`
3. Set ownership: `sudo chown -R vadym.samoilenko:vadym.samoilenko /opt/hm-ems-data /var/www/html/hm-ems-report /var/log/hm-ems`
4. Upload campaign JSON files to `/opt/hm-ems-data/Master_Json/`
5. Upload campaign images to `/opt/hm-ems-data/campaign_images/{year}/{campaign}/Automation_LR/`
6. Configure Apache: add ProxyPass directives for `/hm-ems-report/api` → localhost:5000
7. Run `sudo ./deploy.sh`

## Environment Variables
- `IMAGE_BASE_PATH` — Root folder containing `{year}/{campaign}/Automation_LR/` images (default: `./campaign_images`)
- `MASTER_JSON_DIR` — Folder containing campaign JSON files (default: `./Master_Json`)
- `PORT` — Server port for `python3 server.py` (default: `5000`)
- `WORKERS` — Number of Gunicorn workers (default: `4`)
- `SECRET_KEY` — Flask secret key for sessions (must be set in production)
- `DEPLOY_USER` — User for deployment ownership (default: `vadym.samoilenko`)
- `WEB_DIR` — Static files deployment path (default: `/var/www/html/hm-ems-report`)
- `APP_DIR` — Application code path (default: `/opt/hm_ems_report`)
- `DATA_DIR` — Data directory path (default: `/opt/hm-ems-data`)

## API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/` | Review UI (single-page app) |
| GET | `/api/files` | List available campaign JSON files |
| GET | `/api/load/<filename>` | Load campaign data, images, languages, and approval state |
| POST | `/api/approve` | Approve/unapprove a record, save edits to master JSON, log changes |

## Known Issues
- Application expects consistent JSON structure with `Season`, `Filename`, and article ID fields
- Image conversion from `.tif` to `.jpg` is handled by path mapping; actual `.tif` files must exist in `Automation_LR/`
- No built-in authentication (recent commits added simple login but status unclear); verify `.env` `SECRET_KEY` is configured in production
- Change log replay on startup could be slow with very large changelog files (no pagination/archiving documented)

## Git
- **Remote:** git@bitbucket.org:zlalani/hm_ems_report.git
- **Recent commits:** Focus on deployment/Apache configuration (ProxyFix, redirects, login auth), image path fixes, and systemd service setup

## Sessions
### 2026-04-14 – Project catalogued
**Done:** Added to Obsidian second brain.

---
## Change Log
| Date | Requested | Changed | Files |
|------|-----------|---------|-------|
| 2026-04-14 | Initial setup | Note created | — |

## Related
- [[hm-o2e-tool/HM O2E Tool]]