---
name: "L'Oréal SLA Calculator"
client: L'Oréal
status: active
server: optical-web-1
tech: [JavaScript, Node.js, Express, PostgreSQL, HTML/CSS, Docker]
local_path: /Users/ai_leed/Documents/Projects/Oliver/loreal-sla-calculator
deploy: docker-compose up
url: 
tags: [loreal, sla, timeline, gantt, ecom]
created: 2026-04-14
port: 3100
db: PostgreSQL
---

## Overview

The **loreal-sla-calculator** is a web-based SLA (Service Level Agreement) calculator for L'Oréal's eCom Content Factory that replaces legacy Excel-based workflows. It provides two interfaces: a **Full PM Calculator** (`index.html`) with a 4-step wizard for project managers to estimate timelines and determine deadline feasibility, and a **Client Estimator** (`client.html`) with a simplified single-page form for clients using SLA defaults. The calculator computes project timelines across 8 configurable stages (Production, Translation, Syndication, Opera Upload, etc.), accounts for complexity, asset volume, revision rounds, and market approval days, and outputs results as timeline breakdowns, Gantt charts, and exportable calendars (CSV/iCal).

## Tech Stack

- **Frontend:** JavaScript (vanilla), HTML5, CSS3, responsive design
- **Backend:** Node.js, Express.js
- **Database:** PostgreSQL 16 (Alpine Linux)
- **Infrastructure:** Docker Compose, systemd-compatible deployment
- **Configuration:** JSON-based business rules (non-developer editable)
- **Key libraries:** Nodemon (dev), MSAL (Azure SSO), Analytics tracking

## Architecture

The project uses a **client-server hybrid model**:

```
┌─────────────────────────────────────────────────────────────┐
│                    Frontend (Client-Side)                    │
├─────────────────────────────────────────────────────────────┤
│ index.html (Full PM Calculator)      client.html (Estimator)│
│ • 4-step wizard                       • Single-page form     │
│ • Gantt chart visualization           • Simplified inputs    │
│ • CSV/iCal export                     • Default values       │
│ • Dark mode toggle                    • Market Brief Advisor │
│ • Usage analytics tracking            • SSO integrated       │
└─────────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│              Shared Configuration Layer                      │
├─────────────────────────────────────────────────────────────┤
│ config.json (Business Rules)          script.js / client-script.js
│ • 9 brief types & stage matrix        • Calculation engines  │
│ • Production cross-ref table          • Form dynamics        │
│ • Syndication cross-ref table         • Timeline generation  │
│ • Translation word ranges             • Export logic         │
│ • Default values (revisions, etc.)    • Dark mode persistence│
│ • Tooltips & descriptions             • Analytics events     │
└─────────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│              Backend (Node.js/Express)                       │
├─────────────────────────────────────────────────────────────┤
│ server/index.js (Express app)                               │
│ • Serves static HTML/CSS/JS                                 │
│ • Azure SSO authentication (MSAL)                           │
│ • Analytics event logging API                              │
│ • User session management                                   │
└─────────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│          Database (PostgreSQL 16)                            │
├─────────────────────────────────────────────────────────────┤
│ • Analytics events table                                     │
│ • User activity logs                                         │
└─────────────────────────────────────────────────────────────┘
```

**Key Design Decisions:**
- **Config-driven:** All business rules centralized in `config.json` so PMs/non-devs can edit without code changes
- **Dual interfaces:** Full calculator for power users (PMs), simplified estimator for clients
- **Business-day math:** All calculations use Monday-Friday only (excludes weekends)
- **Syndication buffer:** When syndication is active, 10 business days auto-added to suggested go-live date
- **Parallel stages:** Stages 4 & 5 (Translation PDP + Asset) share one input set and run concurrently
- **Locked Opera Upload:** Always enabled (`alwaysActive: true`); users cannot deactivate it

## Dev Commands

```bash
# Install dependencies
npm install

# Start dev server with hot reload (runs on port 3100)
npm run dev

# Start production server
npm start

# Run database migrations
npm run migrate

# Build Docker image and run locally
docker-compose up

# View logs from Docker container
docker-compose logs -f app
```

## Deployment

- **Server:** Docker Compose (self-hosted on `/opt/loreal-sla-calculator/`)
- **Deploy:** `docker-compose up -d` (restarts services unless stopped)
- **URL:** Accessible via configured domain/reverse proxy (port 3100 internally)
- **Port:** 3100
- **Service:** Managed via Docker Compose (postgres and app services)
- **Local path:** `/Users/ai_leed/Documents/Projects/Oliver/loreal-sla-calculator`

**Deploy Notes:**
- Environment variables loaded from `/opt/loreal-sla-calculator/server/.env`
- PostgreSQL data persists via `postgres_data` Docker volume
- Health checks ensure postgres is ready before app starts
- Requires `.env` file with `POSTGRES_USER`, `POSTGRES_DB`, and other secrets

## Environment Variables

Key environment variables (loaded from `.env`):

- `PORT` — Server port (default: 3100)
- `NODE_ENV` — Set to `production` in production; `development` for local dev
- `SERVE_STATIC` — Whether to serve static files (set to `false` if behind reverse proxy)
- `POSTGRES_USER` — PostgreSQL username
- `POSTGRES_DB` — PostgreSQL database name
- `POSTGRES_PASSWORD` — PostgreSQL password
- `DATABASE_URL` — Full PostgreSQL connection string (if used by app)
- `MSAL_*` — Azure SSO configuration (MSAL client ID, tenant, etc.)
- `ANALYTICS_DB_*` — Analytics table connection details (if separate)

## Calculation Logic

**Core Timeline Calculation:**

For each **active stage**, the system computes:
```
Stage Duration = Handover (1 day) 
               + WIP Days (from cross-ref table) 
               + Market Approval Days 
               + (Revision Days × Revision Rounds)
```

**Cross-Reference Tables (in config.json):**
- **Production:** Days indexed by [complexity level] × [asset volume] (up to 400 assets)
- **Syndication:** Days indexed by [complexity level] × [EAN volume]
- **Translation:** Days indexed by [word count bucket] (e.g., <2K, 2K-5K, 5K+)
- **Opera Upload:** Days indexed by [asset volume]

**Special Rules:**
- **Syndication buffer:** When syndication stage is active, +10 business days added to suggested go-live
- **Stages 4 & 5 parallel:** Both stages use the same complexity/asset inputs and run concurrently (max of both durations)
- **Opera Upload always on:** Cannot be toggled off by users; mandatory for all brief types
- **Business days only:** Uses Monday-Friday calculations (skips weekends)

## Known Issues

- Static work type sub-selector and brief type override dropdown added (commit a1abbbc) — verify UI/UX in edge cases
- Decimal feedback days supported (0.1 step) but may show precision rounding on export
- Analytics dashboard accessible only via direct URL (removed from nav links per commit 28fc722)
- MSAL CDN migration to jsdelivr (commit 8843af6) — ensure cache-busting if issues arise
- SSO redirect URI must match Azure app registration exactly (no trailing slash per commit 53a73cb)

## Git

- **Remote:** `git@bitbucket.org:zlalani/loreal-sla-calculator.git`
- **Recent work:** SSO fixes, analytics dashboard, static imagery & work type selectors, syndication defaults, market brief advisor
- **Latest commit:** 53a73cb — Fix SSO redirect URI (remove trailing slash)

## References & Notes

**For Editing Business Rules:**
Simply edit `config.json` to adjust:
- Brief type definitions & stage application matrix
- Production/Syndication/Translation cross-reference tables
- Complexity levels, asset volume ranges, word count buckets
- Default revision rounds, market approval days, revision days per round
- Help text, tooltips, and syndication buffer value

**For Adding New Features:**
- PM calculator logic: `script.js` (4-step wizard, Gantt, export)
- Client estimator logic: `client-script.js` (simplified form, defaults)
- Backend routes: `server/index.js` (SSO, analytics, static serving)
- DB schema: `server/db/migrate.js` (analytics tables)

**Testing Checklist:**
- [ ] All 9 brief types load and display correct stages
- [ ] Complexity/asset volume lookups match cross-ref tables
- [ ] Syndication +10 day buffer applied correctly
- [ ] Gantt chart renders across full timeline
- [ ] CSV/iCal exports contain all key milestone dates
- [ ] Dark mode persists to localStorage
- [ ] Mobile layout responsive on <768px screens
- [ ] SSO redirects to correct Azure tenant
- [ ] Analytics events logged to database

## 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
- [[loreal-global-kickoff/Loreal Global Kickoff]]