# WepAppOpusApi — System Documentation

> **Production-grade Facebook Ads Management API** for Thai Meta advertisers
> Version: 1.1 (2026-07-31) | Live: https://adsfb.namnan.co.th

---

## 📋 Table of Contents

1. [Overview](#-overview)
2. [Architecture](#-architecture)
3. [Tech Stack](#-tech-stack)
4. [Feature Timeline](#-feature-timeline)
5. [API Reference](#-api-reference)
6. [Database Schema](#-database-schema)
7. [Cron Jobs](#-cron-jobs)
8. [Safety System](#-safety-system)
9. [Webhooks & Integrations](#-webhooks--integrations)
10. [Deployment](#-deployment)
11. [Configuration](#-configuration)
12. [Changelog](#-changelog)

---

## 🎯 Overview

**WepAppOpusApi** is a complete Facebook Marketing API wrapper + ad management system built for **Thai e-commerce advertisers** who run multiple ad campaigns and need:

- **Smart scaling** (bulk + per-product + scheduled + auto-rules)
- **Performance tracking** (daily metrics, forecasts, predictions vs actuals)
- **Real-time chat integration** (Facebook Messenger + webhook + SSE push)
- **Safety guards** (block pause, block scale-down, midnight reset)
- **Multi-page Messenger attribution** (track which ad → which chat)
- **Webhook notifications** (Slack-style POST on scale/auto events)
- **Public live dashboard** (no auth) + **private admin dashboard** (with login)

### Key Numbers (as of 2026-07-12)
- **77** TypeScript source files
- **96** API endpoints
- **23** database tables
- **7** background cron jobs
- **~5,200** lines of business logic
- **6,500+** lines of frontend (HTML/JS/CSS)
- **3,450** historical daily metrics rows (30d × 115 campaigns)

---

## 🏗 Architecture

### High-Level Flow

```
┌─────────────────────────────────────────────────────────────┐
│                       Nginx (port 443)                       │
│  - SSL termination (Let's Encrypt)                           │
│  - Static file serving (/demo/*, /assets/*, /pages/*)      │
│  - Reverse proxy → Fastify (port 22222)                     │
│  - SSE-friendly: proxy_buffering off for /live/chat-stream  │
└────────────────────────┬────────────────────────────────────┘
                         │
┌────────────────────────▼────────────────────────────────────┐
│              Fastify (Node.js + tsx) on :22222              │
│  - 13 route modules (auth, live, admin, products, scale...)  │
│  - 7 cron jobs (every 5min - 6h)                            │
│  - MySQL adapter (mysql2 pool) + SQLite fallback            │
└────────────────────────┬────────────────────────────────────┘
                         │
        ┌────────────────┼────────────────┐
        ▼                ▼                ▼
   ┌─────────┐    ┌──────────┐    ┌──────────────┐
   │ MySQL   │    │  Meta    │    │   Facebook   │
   │ (local) │    │  Graph   │    │   Webhook    │
   │ 11 acct │    │   v20.0  │    │   (POST)     │
   └─────────┘    └──────────┘    └──────────────┘
        ▲                              │
        │                              ▼
        │                    ┌──────────────────┐
        └────────────────────│   Browser SSE   │
              (poll/sync)    │  /live/chat-stream│
                             └──────────────────┘
```

### Directory Structure

```
SJ88lnwadsApi/
├── src/
│   ├── server-shared.ts           # Session helpers, error response
│   ├── config/                     # Runtime config, env validation
│   ├── db/
│   │   ├── sql.ts                  # All 23 CREATE TABLE statements
│   │   ├── adapter.ts              # MySQL/SQLite pool abstraction
│   │   └── run-migrations.ts       # Idempotent migration runner
│   ├── live/                       # Facebook Marketing API wrappers
│   │   ├── campaigns.ts            # listCampaigns, updateCampaignBudget
│   │   ├── budgetControls.ts       # Cap enforcement, audit
│   │   ├── chats.ts                # Messenger conversations per page
│   │   ├── chatTracker.ts          # Daily chat aggregation
│   │   ├── chatAttribution.ts      # ad_id → campaign_id mapping
│   │   ├── forecastTracker.ts      # Predictions vs actuals
│   │   ├── scaleHistory.ts         # logScaleAction (audit log)
│   │   ├── scaleGuards.ts          # Safety guards (pause block, etc.)
│   │   ├── webhookNotifier.ts      # Slack-style webhook dispatch
│   │   └── reallocation.ts         # Auto budget reallocation
│   ├── cron/                       # Background jobs (7 jobs)
│   ├── routes/                     # Fastify route modules (13 files)
│   ├── server-dashboard*.ts        # Dashboard query orchestrators
│   ├── dashboard-*.ts              # Dashboard sub-renderers
│   ├── mapping.ts                  # Meta action_type → DB column mapping
│   ├── ingest/                     # Historical data ingest
│   └── index.ts                    # Fastify app entrypoint
├── public/
│   ├── index.html                  # Landing page
│   ├── live.html                   # Public live dashboard (no auth)
│   ├── dashboard.html              # Private admin dashboard (auth)
│   ├── scale.html                  # Scale Campaigns workspace
│   ├── analytics.html              # Charts + forecast accuracy
│   ├── manual.html                 # User guide
│   ├── api-guide.html              # Meta API guide
│   ├── pages/                      # Static page assets
│   ├── assets/                     # JS + CSS bundles
│   └── changelog.html              # Version history
├── docs/                           # This directory
│   ├── SYSTEM.md                   # ← YOU ARE HERE
│   ├── adr-0001-architecture.md
│   └── db-migration-plan.md
├── pm2.config.cjs                  # PM2 process manager config
└── package.json
```

---

## 🛠 Tech Stack

| Layer | Technology | Version |
|-------|------------|---------|
| **Runtime** | Node.js | 22.x |
| **Language** | TypeScript | 5.x (via tsx runtime) |
| **Server** | Fastify | 4.x |
| **Database** | MySQL | 8.x (with SQLite fallback) |
| **Process Manager** | PM2 | Latest |
| **Web Server** | Nginx | 1.18+ |
| **SSL** | Let's Encrypt | Auto-renewed |
| **Frontend** | Vanilla HTML/CSS/JS | No build step |
| **Charts** | Chart.js | 4.4.7 (CDN) |
| **Fonts** | Fraunces, Sarabun, JetBrains Mono | Google Fonts |
| **External API** | Facebook Graph API | v20.0 |

### Why no build step?
- **Single-file deploy**: each HTML page is self-contained + assets
- **Fast iteration**: edit a file, refresh browser, no compile
- **Easy debugging**: source maps work directly in browser dev tools
- **CDN-friendly**: only static files served, no Node.js build artifacts

---

## 📅 Feature Timeline

### Phase 1-5: Core Dashboard (initial)
- **Live dashboard** with 12 charts (spend/ROAS/impressions/funnel)
- **Period comparison** + **monthly forecast** with confidence band
- **Daily metrics cron** (every 6h, fetches insights from Meta)
- **Campaign list** + **product grouping** (auto-tag from name pattern)
- **30d backfill** button (admin)

### Phase 6.5: Product Categorization
- **Auto-tagging** (regex patterns → product name)
- **Manual assignment** UI (drag-drop or search)
- **Per-product caps** (daily budget limit per product)
- **Trending widget** (top movers by ROAS change)
- **CSV export** (filtered campaigns)

### Phase 6.5c: Scale Campaigns MVP
- **Bulk select** + **preset actions** (+50%, +30%, -30%, Pause, Resume, custom %)
- **Race-free execution** (page sends precomputed newBudget)
- **Live preview** before apply
- **Cap preflight** (product cap, daily total cap)

### Phase 6.5d: Scale Full (Schedule + Rec + Forecast)
- **Scheduled changes** with auto-revert
- **Weekly recurring** (e.g., every Monday at 9am)
- **AI Recommendations** (trend-based: recent 7d vs older 7d)
- **Forecast Impact** (monthly projection with elasticity 0.9/1.1)

### Bundle A: Safety
- **History + Undo** (every scale action logged, revertible)
- **Product Cap Enforce** (reject bulk if would exceed)
- **Daily Total Cap** (config: max_total_daily_budget_thb = 50000)

### Bundle B: Intelligence
- **Trend-based rec** (recent 3d/4d comparison, weighted by trend)
- **Bulk by Product** (apply action to all in product)

### Bundle C: Automation
- **Auto-Scale Rules** (5 conditions × 4 actions × 2 scopes)
- **6h cron** (auto_pause + auto_scale)

### Phase 7: Forecast vs Actual
- **Predictions** stored on every scale action
- **60min evaluator** cron (compares predicted vs actual at 24/48/72h)
- **Drift detection** (fires webhook if actual deviates >25%)

### Phase 8: Webhooks
- **Slack-compatible POST** with HMAC-SHA256 signature
- **10 event types** (scale.applied, auto_rule.executed, schedule.executed, cap.violated, forecast.drift, etc.)
- **Fire-and-forget** with 3-retry (1s, 2s, 4s)
- **10s timeout** per attempt
- **Delivery log** in `webhook_deliveries` table

### Phase 9: Analytics
- **4 Chart.js visualizations** (ROAS/Spend trend, Budget timeline, Action volume, Forecast accuracy)
- **Top stats** (Spend, Revenue, ROAS, Action count)
- **Measurements table** with drift pills

### Phase 10: Live Chats (Facebook Messenger)
- **Public endpoint** `/public/chats` (no auth)
- **Aggregates conversations** across all FB pages
- **Per-page + per-campaign breakdown**
- **Empty state** when no recent chats

### Phase 11: Chat Attribution
- **Messenger URL generator** (`https://m.me/{page}?ref=c_{campaignId}`)
- **Ads Manager template** (uses `{{campaign.id}}` macro)
- **ad_id → campaign_id** resolution via `/adId?fields=campaign_id`

### Phase 12: Chat Daily Cron
- **Tables**: `chat_daily`, `chat_messages`
- **Cron**: every 60 min, fetches all pages, aggregates per day
- **Endpoint**: `/public/chat-stats` (totals, per-day, per-campaign)

### Phase 13: Real-time Webhook + SSE
- **Facebook webhook** at `/webhook/facebook` (GET verification, POST events)
- **HMAC-SHA1 signature** verification
- **SSE stream** at `/live/chat-stream` (30s heartbeat, auto-reconnect)
- **Real-time message push** to all connected clients

### Phase 14: Safety Guards + Midnight Reset
- **Block pause at min budget** (config: min_safe_budget_thb = 39)
- **Block scale-down** (negative %, newBudget < current)
- **Midnight reset cron** (00:00 ICT → reset all ACTIVE to 39 THB)
- **UI section** 🛡️ with toggle buttons

### Phase 15: Global Pause Block
- **Config**: `pause_disabled_globally` (default true)
- **Blocks ALL pause paths**:
  - Manual bulk
  - Scheduled pause
  - Auto-scale rule with pause
  - Auto-pause cron
  - Scale-scheduler executing pause
- **UI**: red ⛔ badges, grayed-out pause preset

---

## 🔌 API Reference

### Total: 96 endpoints across 13 route modules

### Auth Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/auth/login` | public | Login with username/password |
| GET | `/auth/session` | public | Check current session |
| POST | `/auth/logout` | session | Destroy session |
| POST | `/auth/change-password` | session | Change own password |

### Public Endpoints (no auth)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/public/live-dashboard` | Full dashboard data |
| GET | `/public/runtime-config` | Budget rules + realloc config |
| GET | `/public/campaigns` | Campaign list (no PII) |
| GET | `/public/trends` | Daily metrics series |
| GET | `/public/forecast` | Monthly forecast |
| GET | `/public/chats` | Messenger conversations |
| GET | `/public/chat-stats` | Daily chat aggregates |
| GET | `/manual.html` | User guide (no auth) |
| GET | `/demo/login` | Login page |
| GET | `/demo/scale` | Scale workspace (auth) |
| GET | `/demo/analytics` | Analytics charts (auth) |

### Live Endpoints (session required)
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/live/accounts` | analyst | List Meta ad accounts |
| GET | `/live/campaigns` | analyst | List campaigns with budget |
| GET | `/live/products` | analyst | List product groupings |
| POST | `/live/campaigns/bulk` | admin | Bulk scale (with safety guards) |
| GET | `/live/scale-history` | analyst | Action log (last N) |
| GET | `/live/scale-schedules` | analyst | List schedules |
| POST | `/live/scale-schedules` | admin | Create schedule (blocks pause) |
| POST | `/live/scale-schedules/:id/cancel` | admin | Cancel pending schedule |
| GET | `/live/scale-recommendations` | analyst | AI recs (trend-based) |
| POST | `/live/scale-forecast` | analyst | Forecast impact |
| GET | `/live/auto-scale-rules` | analyst | List rules |
| POST | `/live/auto-scale-rules` | admin | Create rule (blocks pause) |
| PATCH | `/live/auto-scale-rules/:id` | admin | Toggle/edit |
| DELETE | `/live/auto-scale-rules/:id` | admin | Remove |
| POST | `/live/auto-scale-rules/:id/test` | admin | Dry-run |
| GET | `/live/products/scale-candidates` | analyst | Products with cap usage |
| POST | `/live/products/:product/scale-bulk` | admin | Bulk by product (blocks pause) |
| GET | `/live/forecast/accuracy` | analyst | Accuracy summary |
| GET | `/live/forecast/measurements` | analyst | Recent predictions vs actuals |
| GET | `/live/chat-stream` | session | **SSE stream for real-time chat** |

### Admin Endpoints (admin role)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/admin/audit` | Audit log |
| POST | `/admin/metrics/run` | Run daily metrics now |
| POST | `/admin/metrics/backfill` | Backfill 1-30 days |
| GET | `/admin/config` | List all config |
| PUT | `/admin/config/:key` | Set config value |
| GET | `/admin/safety-guards` | Current safety config |
| POST | `/admin/midnight-reset/run` | Manual trigger reset |
| POST | `/admin/forecast/evaluate` | Force forecast evaluation |
| GET | `/admin/auto-pause/runs` | Auto-pause history |
| GET | `/admin/webhooks` | List webhooks |
| POST | `/admin/webhooks` | Create webhook |
| PATCH | `/admin/webhooks/:id` | Update webhook |
| DELETE | `/admin/webhooks/:id` | Remove webhook |
| POST | `/admin/webhooks/:id/test` | Test webhook (send to URL) |
| GET | `/admin/webhooks/:id/deliveries` | Delivery log |

### Webhook Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/webhook/facebook` | Facebook verification (hub.mode=subscribe) |
| POST | `/webhook/facebook` | Facebook event delivery |
| GET | `/webhook/facebook/status` | Health check |

### Demo Pages (HTML)
| Path | Description |
|------|-------------|
| `/` | Landing page (public) |
| `/live` | Public live dashboard (no auth) |
| `/demo/login` | Login form |
| `/demo/live-dashboard` | Private admin dashboard (auth) |
| `/demo/scale` | Scale Campaigns workspace (auth) |
| `/demo/analytics` | Charts + forecast accuracy (auth) |
| `/manual.html` | User guide (public) |
| `/api-guide.html` | Meta API integration guide (public) |
| `/changelog.html` | Version history (public) |

---

## 🗄 Database Schema

### 23 Tables (MySQL)

#### Core
- `auth_users` (id, username, password_hash[scrypt], role, created_at)
- `api_checkpoints` (sync state with Meta)
- `auth_user_api_profiles` / `_sources` / `_accounts` / `_audit`
- `app_config` (key-value runtime config)

#### Campaign Data
- `campaign_daily_metrics` (per-campaign per-day spend/revenue/roas/etc)
- `auto_pause_runs` (cron history)

#### Products (Phase 6.5)
- `realloc_snapshots` (auto-realloc history)
- `product_assignments` (campaign_id → product_name)
- `product_tagging_rules` (regex patterns)
- `product_caps` (per-product daily budget limit)

#### Scale Features
- `scale_schedules` (scheduled changes with auto-revert)
- `scale_action_log` (every action, undo-able)
- `auto_scale_rules` (if-this-then-that rules)

#### Webhooks
- `webhooks` (id, name, url, events, secret, enabled)
- `webhook_deliveries` (delivery log for debugging)

#### Forecast
- `forecast_predictions` (predicted spend/revenue on scale)
- `forecast_actual` (measured after 24/48/72h)

#### Chat
- `chat_daily` (per-page per-day conversation counts)
- `chat_messages` (raw messages from webhook)

### Key Relationships
```
campaign_daily_metrics  ← reads from → Meta Insights API
       ↓
product_assignments (campaign_id → product_name)
       ↓
product_caps (per-product budget limit)
       ↓
scale_action_log (every change, can undo)
       ↓
forecast_predictions → forecast_actual (24h+ later)
       ↓
forecast.drift webhook (if actual >25% off)
```

---

## ⏰ Cron Jobs (7 jobs)

| Cron | Interval | Purpose | File |
|------|----------|---------|------|
| `[auto-pause]` | every 1h | Pause high-CPA / low-ROAS campaigns | `cron/autoPause.ts` |
| `[chat-daily]` | every 60min | Aggregate Messenger conversations per day | `cron/chatDailyCron.ts` |
| `[scale-scheduler]` | every 5min | Execute scheduled scale changes | `cron/scaleScheduler.ts` |
| `[forecast-evaluator]` | every 60min | Compare predictions vs actual (24h+) | `cron/forecastEvaluator.ts` |
| `[midnight-reset]` | 00:00 ICT daily | Reset all ACTIVE campaigns to 39 THB | `cron/midnightReset.ts` |
| `[auto-scale]` | every 6h | Evaluate auto-scale rules | `cron/autoScale.ts` (in routes) |
| `[daily-metrics]` | 00:05, 06:05, 12:05, 18:05 ICT | Fetch daily insights from Meta | `cron/dailyMetrics.ts` |

### Cron Order of Operations
1. **00:00 ICT**: midnight-reset runs first (resets all to 39)
2. **00:05 ICT**: daily-metrics runs (5 min later, captures yesterday's spend)
3. **00:05 ICT**: scale-scheduler runs (every 5 min from this point)
4. **Every 5 min**: scale-scheduler checks pending schedules
5. **Every 60 min**: chat-daily + forecast-evaluator
6. **Every 60 min**: auto-pause (hourly)
7. **Every 6h**: auto-scale evaluates rules
8. **Next 00:00**: cycle repeats

---

## 🛡 Safety System

### Three Layers of Protection

#### Layer 1: Per-Request Guard (`scaleGuards.ts`)
- **Block pause at min budget** (config: `min_safe_budget_thb = 39`)
- **Block scale-down** (negative %, newBudget < current)
- **Block pause globally** (config: `pause_disabled_globally = true`)
- Returns: `SAFETY_GUARD_BLOCKED` with detailed violations

#### Layer 2: Per-Campaign Cap (`product_caps` table)
- `max_total_daily_budget_thb = 50000` (across all in bulk)
- Per-product cap (from `product_caps` table)
- Per-campaign cap (`min=20, max=1000` THB)
- Returns: `DAILY_TOTAL_CAP_EXCEEDED` or `PRODUCT_DAILY_CAP_EXCEEDED`

#### Layer 3: Per-Change Limits
- Max budget change: `+100% / -100%` per single action
- Cooldown: 5 min between changes on same campaign
- Daily limit: 50 budget changes per user
- Reason required: min 5 chars (15 for changes > 500 THB)

### Config Keys (app_config)
| Key | Default | Range | Purpose |
|-----|---------|-------|---------|
| `min_campaign_daily_budget_thb` | 20 | 1-1000 | Min budget per campaign |
| `min_safe_budget_thb` | 39 | 1-1000 | Pause block threshold |
| `max_campaign_daily_budget_thb` | 1000 | 100-10000 | Max budget per campaign |
| `max_total_daily_budget_thb` | 50000 | 0-1000000 | Max sum in one bulk action |
| `pause_disabled_globally` | true | bool | Global pause block |
| `block_pause_when_at_min` | true | bool | Block pause at ≤ min_safe |
| `block_scale_down` | true | bool | Block negative % / budget |
| `midnight_reset_budget_thb` | 39 | 0-1000 | Daily reset target (0=disabled) |
| `auto_pause_enabled` | true | bool | Master switch for auto-pause |
| `daily_metrics_cron_hour` | 23 | 0-23 | When to run daily-metrics |
| `daily_metrics_only_active` | true | bool | Only ACTIVE + PAUSED |
| `daily_metrics_min_daily_budget` | 0 | 0-100 | Skip campaigns below this |

---

## 📡 Webhooks & Integrations

### Outgoing Webhooks (10 event types)

```typescript
type WebhookEventType =
  | "scale.applied"        // Bulk scale succeeded
  | "scale.undone"         // Undo executed
  | "scale.failed"         // Scale action error
  | "auto_rule.matched"    // Auto-scale rule triggered
  | "auto_rule.executed"   // Auto-scale completed
  | "schedule.executed"    // Scheduled change fired
  | "schedule.failed"      // Schedule execution error
  | "cap.violated"         // Product or daily cap hit
  | "backfill.completed"   // 30d backfill finished
  | "forecast.drift"       // Actual significantly differs from forecast
  ;
```

### Webhook Payload Format
```json
{
  "event": "scale.applied",
  "timestamp": "2026-07-11T16:28:13.091Z",
  "text": "✋ Manual scale: set_budget on 120230799572200590 (+4 THB)",
  "blocks": [{ "type": "section", "text": { "type": "mrkdwn", "text": "..." }}],
  "data": {
    "actionLogId": 42,
    "campaignId": "120230799572200590",
    "actionType": "set_budget",
    "beforeBudget": 76,
    "afterBudget": 80,
    "delta": 4,
    "source": "manual",
    "actor": "admin"
  }
}
```

### Webhook Headers
```
Content-Type: application/json
User-Agent: WepAppOpusApi-Webhook/1.0
X-Webhook-Signature: sha256=<hmac>  (if secret set)
X-Webhook-Event: scale.applied
```

### Reliability
- **Fire-and-forget** (doesn't block caller)
- **3 retries** with exponential backoff (1s, 2s, 4s)
- **10s timeout** per attempt
- **Delivery log** in `webhook_deliveries` table

### Incoming Webhooks (Facebook Messenger)
- **GET `/webhook/facebook`** — verification (hub.mode=subscribe, hub.verify_token)
- **POST `/webhook/facebook`** — events (messaging, messaging_postbacks)
- **HMAC-SHA1** signature verification (X-Hub-Signature header)
- **SSE push** to `/live/chat-stream` subscribers

---

## 🚀 Deployment

### Local Development
```bash
cd /opt/adsfb
npm install
npx tsx ./src/index.ts  # Runs on :22222
```

### Production (PM2)
```bash
# Build (no build needed - tsx runs TS directly)
pm2 start pm2.config.cjs --update-env
# View logs
pm2 log 0
# Restart
pm2 restart 0 --update-env
```

### Nginx Config
```nginx
server {
    server_name adsfb.namnan.co.th;
    # Static pages
    location = /demo/analytics { root /opt/adsfb/public; try_files /analytics.html =404; }
    location = /demo/scale { root /opt/adsfb/public; try_files /scale.html =404; }
    location = /demo/login { root /opt/adsfb/public; try_files /login.html =404; }
    # SSE-friendly
    location ~* ^/live/chat-stream$ {
        proxy_pass http://127.0.0.1:22222;
        proxy_buffering off;
        proxy_read_timeout 86400s;
    }
    # FB webhook
    location = /webhook/facebook {
        proxy_pass http://127.0.0.1:22222;
    }
    # Everything else → Fastify
    location / { proxy_pass http://127.0.0.1:22222; }
    # SSL (managed by Certbot)
    listen 443 ssl;
}
```

### Migrations
```bash
cd /opt/adsfb
npx tsx ./src/db/run-migrations.ts
# Idempotent — safe to run multiple times
```

### Environment Variables (.env)
```bash
# Server
PORT=22222
NODE_ENV=production
LOG_LEVEL=info

# Database
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=...
DB_NAME=adsfb

# Auth
LOGIN_PASSWORD=oSu86wowB10fZT6rjOc3Q7Ap  # Fallback only (DB hash is primary)
SESSION_SECRET=...
AUTH_COOKIE_NAME=adsfb_session

# Meta
API_ACCESS_TOKEN_ENC=...  # Encrypted FB access token
AD_ACCOUNT_ID=act_2807991909289645

# Facebook Webhook (optional)
FB_WEBHOOK_VERIFY_TOKEN=sj88-webhook-2026
FB_APP_SECRET=...  # For HMAC signature verification
```

---

## ⚙ Configuration

### Runtime Config (app_config table)
All values can be changed at runtime via `PUT /admin/config/:key` without restart.

| Key | Default | Where Used |
|-----|---------|------------|
| `daily_metrics_cron_hour` | 23 | daily-metrics cron |
| `daily_metrics_only_active` | true | Filter ACTIVE+PAUSED only |
| `daily_metrics_min_daily_budget` | 0 | Skip low-budget campaigns |
| `max_campaign_daily_budget_thb` | 1000 | Per-campaign cap |
| `min_campaign_daily_budget_thb` | 20 | Per-campaign floor |
| `max_daily_budget_changes_per_user` | 50 | Rate limit |
| `reallocation_max_daily_per_user` | 10 | Auto-realloc rate limit |
| `min_safe_budget_thb` | 39 | Pause block threshold |
| `pause_disabled_globally` | true | Global pause block |
| `block_pause_when_at_min` | true | Per-budget pause block |
| `block_scale_down` | true | Block negative changes |
| `midnight_reset_budget_thb` | 39 | Daily reset target |
| `max_total_daily_budget_thb` | 50000 | Bulk action cap |
| `auto_pause_enabled` | true | Auto-pause master switch |
| `auto_pause_min_spend` | 100 | Min spend before consider |
| `auto_pause_max_cpa` | 500 | Max CPA threshold |
| `auto_pause_min_roas` | 1.0 | Min ROAS threshold |
| `auto_pause_consecutive_days` | 3 | Days before trigger |
| `auto_pause_max_per_run` | 10 | Max pauses per hour |

---

## 📝 Changelog

### v1.1 (2026-07-31) — Heatmap Hourly Dashboard Fix
- **Hourly Heatmap**: fixed per-hour `delta_spend` (was always negative due to JS Date reference comparison)
- Commits: `bdb814b` (sort comparator fix) + `3edfebd` (cross-day prev reset)
- Added: `docs/reports/heatmap_hourly_dashboard_qa_20260731_140800/` (5/5 testcases PASS)
- Added: section 10 in `PROJECT_RULES.md`, section 12 in `docs/PROJECT_RULES.md` (JS Date Equality)
- See: `docs/CHANGELOG-2026-07-31.md` for full details
- Heatmap endpoint: `GET /public/hourly-metrics` now returns positive deltas
- Per-hour verification: 24/24 UI<->API match, 0 negative deltas

### v1.0 (2026-07-12) — Current
- 77 source files, 96 endpoints, 23 tables, 7 crons
- Full feature set: Scale + Chat + Webhooks + Forecast + Safety
- Documentation: this file + ADR-0001 + DB migration plan


### v0.9 (2026-07-11) — Beta
- 67 deploys of SJ88-Video-Editor-Pro (different project, not this one)
- Wait, that's a different project...

### v0.7 (2026-07-10) — Phase C refactor
- Split 1,816-line `live.html` into 11 files
- Split 2,069-line `index.ts` into 13 modules
- All files ≤ 500 lines

### v0.6 (2026-07-10) — Phase 6.5 + 7
- Product categorization + caps
- Period comparison + monthly forecast
- 7-day ROAS trend + top movers

### v0.5 (2026-07-09) — Initial Dashboard
- 12 charts (spend/ROAS/funnel/heatmap)
- Daily metrics cron
- Live dashboard (public + private)

---

## 🤝 Support

- **GitHub**: https://github.com/lnwsj/SJ88-Ads-API (private)
- **Live**: https://adsfb.namnan.co.th
- **Author**: Mavis (root session)
- **Last Updated**: 2026-07-31

---

## 📚 See Also

- [ADR-0001: Architecture Decisions](./adr-0001-architecture.md)
- [DB Migration Plan](./db-migration-plan.md)
- [User Manual (HTML)](../public/manual.html) — `https://adsfb.namnan.co.th/manual.html`
- [API Guide (HTML)](../public/api-guide.html) — `https://adsfb.namnan.co.th/api-guide.html`
