[object Object]

← back to Beverlyhillsvideos

stores: Rodeo Drive store-film pipeline (typed stream, restaurants-only poster preserved)

fbefcdb07823b726a71560eb359fe944863397c1 · 2026-08-06 14:20:36 -0700 · Steve Abrams

- data/stores.json: canonical Rodeo Drive directory (85 current + 12 verify),
  promo flag per store (logo shown only for paid promo/collab; else name-only
  nominative treatment matching the restaurant films).
- filmgen/heygen_batch.mjs: FILM_KIND env → separate ledger/films/scripts per
  kind; store films tagged type:store, prompt uses boutique label. Backward
  compatible (restaurant defaults unchanged).
- social/poster: BHV_IG_KIND slot (restaurant|store), own ledger + film file;
  legacy no-type entries treated as restaurant. Store slot degrades gracefully.
- data/store-scripts.json: 8 flagship pilot scripts (factual/general).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit fbefcdb07823b726a71560eb359fe944863397c1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 6 14:20:36 2026 -0700

    stores: Rodeo Drive store-film pipeline (typed stream, restaurants-only poster preserved)
    
    - data/stores.json: canonical Rodeo Drive directory (85 current + 12 verify),
      promo flag per store (logo shown only for paid promo/collab; else name-only
      nominative treatment matching the restaurant films).
    - filmgen/heygen_batch.mjs: FILM_KIND env → separate ledger/films/scripts per
      kind; store films tagged type:store, prompt uses boutique label. Backward
      compatible (restaurant defaults unchanged).
    - social/poster: BHV_IG_KIND slot (restaurant|store), own ledger + film file;
      legacy no-type entries treated as restaurant. Store slot degrades gracefully.
    - data/store-scripts.json: 8 flagship pilot scripts (factual/general).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/store-scripts.json      |  10 +++++
 data/stores.json             | 105 +++++++++++++++++++++++++++++++++++++++++++
 filmgen/heygen_batch.mjs     |  16 ++++---
 social/poster/post-daily.mjs |  15 +++++--
 4 files changed, 138 insertions(+), 8 deletions(-)

diff --git a/data/store-scripts.json b/data/store-scripts.json
new file mode 100644
index 0000000..4687ce7
--- /dev/null
+++ b/data/store-scripts.json
@@ -0,0 +1,10 @@
+{
+  "Gucci": "On Rodeo Drive, the Gucci flagship is one of Beverly Hills' great fashion houses — Florentine heritage, bold Italian design, and, above the store, the Michelin-starred Gucci Osteria. As much a destination as a boutique.",
+  "Chanel": "Chanel on Rodeo Drive — the house of Coco Chanel. Timeless tweed, the little black dress, and No. 5. One of the most storied names in fashion, in one of Beverly Hills' most photographed boutiques.",
+  "Louis Vuitton": "Louis Vuitton on Rodeo Drive — the French maison known the world over for its monogram, its trunks, and its savoir-faire. A flagship that anchors Beverly Hills' luxury shopping like few others.",
+  "Dior": "Christian Dior on Rodeo Drive — Parisian couture, the New Look, and a boutique as elegant as the house itself. A cornerstone of Beverly Hills style, season after season.",
+  "Cartier": "Cartier on Rodeo Drive — the jeweler of kings. Known for the Love bracelet, the Tank watch, and more than a century of high jewelry. One of Beverly Hills' most enduring addresses.",
+  "Tiffany & Co.": "Tiffany & Co. on Rodeo Drive — the American jeweler behind the robin's-egg blue box, the diamond, and a legacy of romance. A Beverly Hills icon for generations.",
+  "Hermès": "Hermès on Rodeo Drive — the French house of the Birkin and the Kelly, of silk scarves and saddle-stitched leather. Craftsmanship that quietly defines luxury.",
+  "Prada": "Prada on Rodeo Drive — Milanese minimalism, sharp tailoring, and a boutique that reads like a gallery. A modern pillar of Beverly Hills fashion."
+}
diff --git a/data/stores.json b/data/stores.json
new file mode 100644
index 0000000..6bc49c3
--- /dev/null
+++ b/data/stores.json
@@ -0,0 +1,105 @@
+{
+  "note": "Rodeo Drive stores for the BHV store-film series. VERIFY OPEN before filming (tenants change). 'queued' = on the current official Rodeo Drive Committee directory (rodeodrive-bh.com); 'verify' = only on the older aggregator (rodeodrive-shop.com), confirm still open first. Editorial treatment mirrors the restaurant films: store NAME in BHV's own Cormorant type + editorial voice + independent-guide stance (nominative fair use). Brand logo artwork / campaign imagery is NOT reproduced unless promo:true (an actual paid promo/collab where the brand participates).",
+  "style": "Velvet Standard luxury, 16:9, stock presenter, ~22s",
+  "source": "rodeodrive-bh.com/directory (primary) + rodeodrive-shop.com/alpha (supplement)",
+  "stores": [
+    {"name":"Abel Richard","category":"Fashion","promo":false,"status":"verify"},
+    {"name":"Alexander McQueen","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Amiri","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Audemars Piguet","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Balenciaga","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Bottega Veneta","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Breguet","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Breitling","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Brioni","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Brunello Cucinelli","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Buccellati","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Bulgari","category":"Jewelry / Watches","promo":false,"status":"queued"},
+    {"name":"Burberry","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Canali","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Carolina Herrera","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Cartier","category":"Jewelry / Watches","promo":false,"status":"queued"},
+    {"name":"Celine","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Chanel","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"David Webb","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"David Yurman","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Dior","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Dior Homme","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Dolce & Gabbana","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Ermanno Scervino","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Etro","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Fendi","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Frette Fine Linens","category":"Home","promo":false,"status":"queued"},
+    {"name":"Gabriela Hearst","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Giorgio Armani","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Givenchy","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Golden Goose","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Goyard","category":"Accessories","promo":false,"status":"queued"},
+    {"name":"Gucci","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Harry Winston","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Henry Jacques","category":"Fragrance","promo":false,"status":"queued"},
+    {"name":"Hermès","category":"Fashion / Accessories","promo":false,"status":"queued"},
+    {"name":"House of Bijan","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Hublot","category":"Watches","promo":false,"status":"queued"},
+    {"name":"IWC Schaffhausen","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Jaeger-LeCoultre","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Jason of Beverly Hills","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Jimmy Choo","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Kith","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Lanvin","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Loewe","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Loro Piana","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Louis Vuitton","category":"Fashion / Accessories","promo":false,"status":"queued"},
+    {"name":"Max Mara","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"MCM","category":"Accessories","promo":false,"status":"queued"},
+    {"name":"Michael Kors","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Mikimoto","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Moncler","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Panerai","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Patek Philippe","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Persol","category":"Eyewear","promo":false,"status":"queued"},
+    {"name":"Peter Marco","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Philipp Plein","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Piaget","category":"Watches / Jewelry","promo":false,"status":"queued"},
+    {"name":"Pomellato","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Porsche Design","category":"Accessories","promo":false,"status":"queued"},
+    {"name":"Prada","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Ralph Lauren","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Richard Mille","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Rimowa","category":"Luggage","promo":false,"status":"queued"},
+    {"name":"Saint Laurent","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Saint Laurent Rive Droite","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Salvatore Ferragamo","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Shinobi Menswear","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Stefano Ricci","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Stuart Weitzman","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Tiffany & Co.","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Tod's","category":"Fashion / Accessories","promo":false,"status":"queued"},
+    {"name":"Tom Ford","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Tory Burch","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Valentino","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Valmont","category":"Beauty / Skincare","promo":false,"status":"queued"},
+    {"name":"Van Cleef & Arpels","category":"Jewelry","promo":false,"status":"queued"},
+    {"name":"Versace","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Vilebrequin","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Westime","category":"Watches","promo":false,"status":"queued"},
+    {"name":"Winn-Slavin","category":"Jewelry / Fine Art","promo":false,"status":"queued"},
+    {"name":"Wolford","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Zadig & Voltaire","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Zegna","category":"Fashion","promo":false,"status":"queued"},
+    {"name":"Rodeo Collection","category":"Shopping destination","promo":false,"status":"queued"},
+    {"name":"Two Rodeo Drive","category":"Shopping destination","promo":false,"status":"queued"},
+
+    {"name":"Anne Fontaine","category":"Fashion","promo":false,"status":"verify"},
+    {"name":"Battaglia","category":"Fashion","promo":false,"status":"verify"},
+    {"name":"Galerie Michael","category":"Fine Art","promo":false,"status":"verify"},
+    {"name":"Gearys on Rodeo","category":"Jewelry / Home","promo":false,"status":"verify"},
+    {"name":"Lalique","category":"Crystal / Home","promo":false,"status":"verify"},
+    {"name":"Marina Rinaldi","category":"Fashion","promo":false,"status":"verify"},
+    {"name":"Agent Provocateur","category":"Lingerie","promo":false,"status":"verify"},
+    {"name":"Alice + Olivia","category":"Fashion","promo":false,"status":"verify"},
+    {"name":"Bally","category":"Fashion","promo":false,"status":"verify"},
+    {"name":"Bang & Olufsen","category":"Audio","promo":false,"status":"verify"},
+    {"name":"La Perla","category":"Lingerie","promo":false,"status":"verify"}
+  ]
+}
diff --git a/filmgen/heygen_batch.mjs b/filmgen/heygen_batch.mjs
index 19a0c1c..c9e591e 100644
--- a/filmgen/heygen_batch.mjs
+++ b/filmgen/heygen_batch.mjs
@@ -7,9 +7,15 @@ import { execSync } from 'child_process';
 import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
 
 const ROOT = new URL('..', import.meta.url).pathname;
-const LEDGER = ROOT + 'data/heygen-batch.jsonl';
-const SCRIPTS = ROOT + 'data/film-scripts.json';
-const FILMS = ROOT + 'data/films.json';
+// KIND runs a separate typed stream (restaurant | store) with its own ledger + films
+// file, so store films never leak into the restaurants-only IG poster. Defaults preserve
+// the original restaurant behavior byte-for-byte.
+const KIND = process.env.FILM_KIND || 'restaurant';
+const KIND_LABEL = KIND === 'store' ? 'luxury boutique / store' : 'restaurant';
+const _sfx = KIND === 'restaurant' ? '' : `-${KIND}`;
+const LEDGER = ROOT + `data/heygen-batch${_sfx}.jsonl`;
+const SCRIPTS = ROOT + `data/${KIND === 'restaurant' ? 'film' : KIND}-scripts.json`;
+const FILMS = ROOT + `data/${KIND === 'restaurant' ? 'films' : KIND + '-films'}.json`;
 const LOCS = ROOT + 'data/map-locations.json';
 const CONCURRENCY = 3;
 const FLOOR = 130;              // stop submitting if wallet drops below this
@@ -61,7 +67,7 @@ function updateFilms(rows, scripts) {
         if (s.length <= 100) blurb = s;
         else { const cut = s.slice(0,100); blurb = cut.slice(0, cut.lastIndexOf(' ')) + '…'; }
       }
-      films.push({ name: r.name, slug: r.slug, blurb, src: `/video/${r.slug}.mp4`, poster: `/video/${r.slug}.jpg` });
+      films.push({ name: r.name, slug: r.slug, type: KIND, blurb, src: `/video/${r.slug}.mp4`, poster: `/video/${r.slug}.jpg` });
     }
   }
   writeFileSync(FILMS, JSON.stringify(films, null, 1));
@@ -125,7 +131,7 @@ function main() {
     if (inflight() >= CONCURRENCY) break;
     if (wnow !== null && wnow < FLOOR) { console.log(`  ⛔ wallet $${wnow} < floor $${FLOOR} — not submitting`); break; }
     const script = scripts[name];
-    const prompt = `Create a SHORT ~25 second editorial spotlight video for a Beverly Hills city-guide website about the restaurant ${name}. Use EXACTLY this narration and do not lengthen it: "${script}". Elegant, warm, upscale editorial tone. Landscape 16:9.`;
+    const prompt = `Create a SHORT ~25 second editorial spotlight video for a Beverly Hills city-guide website about the ${KIND_LABEL} ${name}. Use EXACTLY this narration and do not lengthen it: "${script}". Elegant, warm, upscale editorial tone. Landscape 16:9.`;
     try {
       const out = jget(sh(`heygen video-agent create --orientation landscape --prompt ${JSON.stringify(prompt)} 2>/dev/null`));
       const d = out.data || out;
diff --git a/social/poster/post-daily.mjs b/social/poster/post-daily.mjs
index 47bf03f..73bbdb7 100644
--- a/social/poster/post-daily.mjs
+++ b/social/poster/post-daily.mjs
@@ -13,8 +13,12 @@ import { postReel } from './ig-graph.mjs';
 
 const HERE = path.dirname(fileURLToPath(import.meta.url));
 const ROOT = path.resolve(HERE, '../..');
-const FILMS = path.join(ROOT, 'data/films.json');
-const LEDGER = path.join(HERE, 'ledger.jsonl');
+// KIND = which daily slot this run serves. 'restaurant' (default) reads films.json;
+// 'store' reads store-films.json — each with its own ledger so the two slots rotate
+// independently. Restaurants stay restaurants-only regardless of store films existing.
+const KIND = process.env.BHV_IG_KIND || 'restaurant';
+const FILMS = path.join(ROOT, KIND === 'restaurant' ? 'data/films.json' : `data/${KIND}-films.json`);
+const LEDGER = path.join(HERE, KIND === 'restaurant' ? 'ledger.jsonl' : `ledger-${KIND}.jsonl`);
 
 function readLedger() {
   try {
@@ -61,7 +65,12 @@ async function pickNextFilm(films, ledger) {
 }
 
 async function main() {
-  const films = JSON.parse(fs.readFileSync(FILMS, 'utf8'));
+  if (!fs.existsSync(FILMS)) {
+    console.log(`[bhv-ig] no ${KIND} film library yet (${path.basename(FILMS)}) — nothing to post.`);
+    return;
+  }
+  // Legacy restaurant entries predate the `type` field, so treat missing type as restaurant.
+  const films = JSON.parse(fs.readFileSync(FILMS, 'utf8')).filter((f) => (f.type || 'restaurant') === KIND);
   const ledger = readLedger();
   const film = await pickNextFilm(films, ledger);
   if (!film) {

← 12a0de5 social: BHV Instagram daily poster (Graph API, restaurants-o  ·  back to Beverlyhillsvideos  ·  stores: bake IP/copyright compliance guardrails into store-f 58e6a6f →