# ADR-095: BTL Client Deck Contract — PowerPoint Add-in, Tagged Shape Naming, and 3-Path Extraction

## Status

Accepted, 2026-07-01.

## Status History

```yaml
status_history:
  - date: 2026-07-01
    status: Proposed
    changed_by: hkl
    reason: |
      Clients (KFC, McDonald's, HP, Lenovo) prepare BTL installation briefs as freeform
      PowerPoint decks and share them with 360LM for production/installation. Currently
      these arrive in inconsistent formats — data must be manually re-entered into the
      Installation PWA, causing friction and errors. Documenting the Add-in architecture,
      tagged shape naming contract, and extraction fallback strategy before rolling out
      to first client.
    changed_via: adr-kit (360lm)
  - date: 2026-07-01
    status: Accepted
    changed_by: hkl
    reason: |
      Discipline at source is the right investment: structured entry on the client side
      eliminates extraction errors and manual re-entry without changing the client's tool
      (they still use PowerPoint). VBA .potm chosen over Office.js for zero-friction
      rollout (no IT approval needed). Three-path extraction ensures backward compat with
      legacy freeform decks. Tagged shape contract (lm360_*) is stable and must be
      honoured by both the Add-in and the BTL Extractor skill going forward.
    changed_via: adr-kit (360lm)
```

## Context

For brands like KFC and McDonald's, the installation workflow does **not** begin with a 360LM Recce. The client already holds their branding specifications and initiates the process by sharing a PowerPoint deck detailing counter-wise installation requirements.

These decks arrive in freeform layouts — different clients use different column orders, merged cells, mixed units, abbreviations, and page structures. Extracting structured data requires the `btl-ppt-extractor` skill, which uses AI parsing and is error-prone when the deck deviates from expected patterns.

**Root cause:** data quality is decided at the client's desk, not at 360LM's. We need to apply discipline at source.

**Options considered:**

| Option | Approach | Pros | Cons |
|--------|----------|------|------|
| A | Continue AI extraction of freeform decks | No change for client | Errors persist; 1–3 manual corrections per deck |
| B | Send clients a fixed Excel template | Structured data | Clients resist switching tools; Excel loses creative context |
| C | Office.js web Add-in | Modern, cross-platform, live API push | Requires IT approval for enterprise clients; needs hosting |
| **D** | VBA macro-enabled template (.potm) | Client uses PowerPoint they already have; zero IT approval; works offline | Windows-only |
| E | Both C + D | Maximum reach | Build cost 2× |

**Decision Maker:** hkl

## Decision

### 1. Client-Side Tool: VBA Macro-Enabled Template (.potm) — Option D

Clients receive a `.potm` template. On first use they save it as `.pptm` (macro-enabled presentation) and import four VBA modules. Subsequent use: open the `.pptm`, fill via Add-in form, email to 360LM.

Rationale: KFC and McDonald's use Windows desktop PowerPoint. No IT approval needed for a macro-enabled file (vs Office.js Add-in manifest registration). The friction of the one-time setup (5 min, documented in `INSTALL.md`) is lower than the ongoing friction of manual re-entry at 360LM's end.

**Office.js Add-in (Option C) is deferred** — build when a client needs Mac/web PowerPoint support or when IT approval can be arranged for enterprise accounts.

### 2. Custom Ribbon Tab: "360LM Branding"

The Add-in adds one ribbon tab with five actions:

| Button | VBA callback | Purpose |
|--------|-------------|---------|
| Edit Cover | `EditCover` | Open form for Brand / Campaign / Date on cover slide |
| New Counter | `NewCounter` | Open `frmNewCounter` — structured form → generates slide |
| Validate | `ValidateAll` | Check all slides for missing names, sizes, unrecognised types |
| Export JSON | `ExportJSON` | Write `360LM_BTL_<Brand>_<Campaign>_YYYYMMDD.json` alongside the PPTX |
| Email to 360LM | `EmailTo360LM` | Save copy + attach JSON → open Outlook draft to `360degreemktg@gmail.com` |

### 3. Tagged Shape Naming Contract (lm360_* schema)

All shapes written by the Add-in — and read by the BTL Extractor — follow this naming convention. **Shape names are the API contract between Add-in and extractor; they must not be changed without updating both.**

#### Cover slide shapes

| Shape name | Contains |
|------------|---------|
| `lm360_deck_title` | Deck title text |
| `lm360_brand` | Brand name |
| `lm360_campaign` | Campaign name |
| `lm360_date` | Date string |
| `lm360_prepared_by` | Preparer name / company |
| `lm360_logo_placeholder` | Client logo drop zone |

#### Counter slides (N = counter number, integer)

| Shape name | Contains |
|------------|---------|
| `lm360_counter_N` | Counter / store name |
| `lm360_city_state_N` | `"City  |  State"` |
| `lm360_brand_tag_N` | Brand name (pill shape) |
| `lm360_creative_N` | Creative image drop zone |
| `lm360_json_N` | **Hidden** — full JSON for counter N, off right edge of slide |

#### Item row shapes (N = counter, R = 0-indexed row)

| Shape name | Contains |
|------------|---------|
| `lm360_N_R_type` | Item type (e.g. "Glow Sign Board") |
| `lm360_N_R_material` | Material (e.g. "Acrylic + LED") |
| `lm360_N_R_size` | Size as string (e.g. "4×2 ft") |
| `lm360_N_R_qty` | Quantity (integer string) |
| `lm360_N_R_rem_prod` | Production remarks |
| `lm360_N_R_rem_inst` | Installation remarks |

#### Embedded JSON shape (`lm360_json_N`)

Each counter slide carries a hidden textbox positioned off the right edge (left > slide width). It contains the counter's full data as a JSON string. Schema:

```json
{
  "lm360_schema": "v1",
  "counter_no": 1,
  "counter_name": "Abohar — KFC",
  "city": "Abohar",
  "state": "Punjab",
  "brand": "KFC",
  "campaign": "Summer Launch 2026",
  "items": [
    {
      "type": "Glow Sign Board",
      "material": "Acrylic + LED",
      "size": "4×2 ft",
      "qty": 1,
      "rem_prod": "Double-sided",
      "rem_inst": "Main entrance",
      "status": "ready"
    }
  ]
}
```

`status` values: `ready` | `recce_pending` | `mockup_pending` | `unclear`

### 4. Three-Path Extraction in BTL Extractor Skill (v3)

The `btl-ppt-extractor` skill checks for Add-in decks before falling back to AI parsing:

```
Path A — Embedded JSON (lm360_json_* shapes present)
  → Parse JSON directly. Zero AI. Zero error. Use when deck was created with Add-in.

Path B — Tagged shapes (lm360_counter_* shapes present, no embedded JSON)
  → Read shape-by-name. Structured but no JSON. Use when Add-in template was used
    but JSON shapes were deleted or corrupted.

Path C — AI extraction (no lm360_* shapes)
  → Original AI parsing pipeline. Use for legacy freeform decks from clients
    who have not yet adopted the Add-in.
    Log: ⚠️ Path C: AI extraction (unstructured deck)
```

**Rule:** never call AI parsing if Path A or Path B succeeds. The log line at Path C entry is mandatory so 360LM staff know to nudge the client toward the Add-in.

### 5. Recognised Item Types

The Add-in form dropdown and the Validate module enforce this list. The BTL Extractor uses the same list for status classification:

```
Glow Sign Board, Backlit Board, Flex Banner, ACP Board, One Way Vision,
Window Decal, Floor Graphic, Translit, Fabric Board, Lollypop,
Clip on Board, Vinyl Print, Inshop Branding
```

Items with unrecognised types are flagged `status: unclear` in exported JSON.

### 6. File Locations

| File | Path |
|------|------|
| Template generator | `tools/btl-addin/generate_template.py` |
| PPTX template | `tools/btl-addin/360LM_BTL_Template.pptx` |
| Client install guide | `tools/btl-addin/INSTALL.md` |
| VBA ribbon module | `tools/btl-addin/vba/Module_Main.bas` |
| VBA JSON exporter | `tools/btl-addin/vba/Module_JSON.bas` |
| VBA validator | `tools/btl-addin/vba/Module_Validate.bas` |
| VBA slide builder | `tools/btl-addin/vba/Module_SlideBuilder.bas` |
| VBA data-entry form | `tools/btl-addin/vba/frmNewCounter.frm` |
| BTL Extractor skill | `~/.claude/remote/plugins/*/skills/btl-ppt-extractor/SKILL.md` |

## Consequences

**Positive:**
- Clients entering data via Add-in → zero extraction errors, zero re-entry at 360LM
- Three-path fallback means legacy freeform decks still work (no forced migration cutover)
- Tagged shape contract is stable — Add-in and extractor are decoupled; either can be upgraded independently
- VBA approach requires no server infrastructure and no client IT approval

**Negative / Watch:**
- Windows-only for Add-in; Mac/web PowerPoint users get template-only (structured slides but no ribbon)
- One-time setup (import .bas files) may confuse non-technical client contacts — `INSTALL.md` and a screen-share walkthrough are recommended for first client
- VBA macros may be blocked by enterprise Group Policy in some organisations — Office.js Add-in (Option C) is the escape hatch

**Future:**
- When a client needs Mac/web support: build Office.js Add-in hosted at `/btl-addin/` on the VPS
- When Installation PWA import endpoint is built (`/installation-proxy/import-btl`): wire the "Email to 360LM" button to POST JSON directly instead of email attachment
- If client count grows > 5: consider publishing the Add-in to Microsoft AppSource for one-click install

## References

- `tools/btl-addin/` — Add-in source (commit `f06676c`)
- `~/.claude/remote/plugins/*/skills/btl-ppt-extractor/SKILL.md` — BTL Extractor skill v3
- ADR-063 — AI dev toolchain (Claude Code + skills)
- ADR-077 — OpenClaw integration
