← back to Settlement Review

scripts/export.py

133 lines

#!/usr/bin/env python3
"""Export a static JSON snapshot of every design that still needs a settlement answer.

Sources (read-only):
  1. Mac2 dw_unified.tk10484_settlement_hold (+ jeffrey_stevens_catalog for images/urls)
  2. ~/Projects/dw-settlement-audit/verdicts.jsonl  (NEEDS_REVIEW, ERROR, PROHIBITED)
     enriched with vendor/sku/created_at from the local shopify_products mirror.

Writes data/items.json. The deployed server only ever reads this file.
"""
import json, os, subprocess, datetime

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, '..', 'data', 'items.json')
AUDIT = os.path.expanduser('~/Projects/dw-settlement-audit/verdicts.jsonl')


def psql_json(sql):
    out = subprocess.check_output(
        ['psql', '-h', '/tmp', '-d', 'dw_unified', '-At', '-c',
         f"select coalesce(json_agg(t),'[]') from ({sql}) t"], text=True)
    return json.loads(out.strip() or '[]')


items = []

# ---- 1. Jeffrey Stevens hold ----
hold = psql_json("""
  select h.id, h.sku, h.name, h.recovered_mfr_code, h.settlement_status, h.cost_missing,
         h.mfr_code_missing, h.provenance, h.held_at, j.image_url, j.product_url, j.all_images
  from tk10484_settlement_hold h
  left join jeffrey_stevens_catalog j on j.id = h.id
  order by h.id""")
for r in hold:
    try:
        imgs = [u for u in json.loads(r['all_images'] or '[]') if u]
    except Exception:
        imgs = []
    if r['image_url'] and r['image_url'] not in imgs:
        imgs.insert(0, r['image_url'])
    primary = r['image_url']
    # the vendor feed sometimes puts a brand-logo .png first; lead with the first photo instead
    if primary and '.png' in primary.lower():
        alt = next((u for u in imgs if '.png' not in u.lower()), None)
        if alt:
            primary = alt
    items.append({
        'key': f"js:{r['id']}",
        'queue': 'jstevens',
        'source': 'TK-10484 JStevens hold',
        'title': r['name'],
        'sku': r['sku'],
        'mfr_code': r['recovered_mfr_code'],
        'vendor': 'Jeffrey Stevens',
        'status': r['settlement_status'],
        'reason': r['provenance'],
        'flags': [f for f, on in (('cost missing', r['cost_missing']), ('mfr code missing', r['mfr_code_missing'])) if on],
        'image': primary,
        'images': imgs,
        'link': r['product_url'],
        'date': r['held_at'],
        'date_label': 'held',
    })

# ---- 2. Catalog settlement audit ----
audit = []
with open(AUDIT) as f:
    for line in f:
        line = line.strip()
        if not line:
            continue
        d = json.loads(line)
        if d.get('verdict') in ('NEEDS_REVIEW', 'ERROR', 'PROHIBITED'):
            audit.append(d)

ids = sorted({str(d['id']) for d in audit})
meta = {}
if ids:
    rows = psql_json(f"""
      select distinct on (shopify_id) replace(shopify_id,'gid://shopify/Product/','') as sid, vendor, coalesce(dw_sku, sku, variant_sku) as sku,
             mfr_sku, created_at_shopify, status
      from shopify_products where shopify_id in ({','.join("'gid://shopify/Product/" + i + "'" for i in ids)})
      order by shopify_id, synced_at desc nulls last""")
    meta = {r['sid']: r for r in rows}

qmap = {'NEEDS_REVIEW': 'review', 'ERROR': 'errors', 'PROHIBITED': 'prohibited'}
for d in audit:
    m = meta.get(str(d['id']), {})
    title = d.get('title') or ''
    vendor = m.get('vendor') or (title.split('|')[-1].strip() if '|' in title else '')
    elems = d.get('partBElements') or []
    parts = []
    if 'partA' in d:
        parts.append(f"Part A {d.get('partA')} (a1={d.get('a1')} a2={d.get('a2')} a3={d.get('a3')})")
    if 'partB' in d:
        parts.append(f"Part B {d.get('partB')}" + (f" [{', '.join(elems)}]" if elems else ''))
    items.append({
        'key': f"audit:{d['id']}",
        'queue': qmap[d['verdict']],
        'source': 'Catalog settlement audit (May 2026)',
        'title': title,
        'sku': m.get('sku') or d.get('handle'),
        'mfr_code': m.get('mfr_sku'),
        'vendor': vendor,
        'status': d['verdict'] + (' · uncertain' if d.get('uncertain') else ''),
        'reason': d.get('reason'),
        'flags': parts + [f"shopify: {m.get('status') or d.get('status')}", f"keyword: {d.get('match')}"],
        'image': d.get('imageUrl'),
        'images': [d['imageUrl']] if d.get('imageUrl') else [],
        'link': f"https://designerwallcoverings.com/products/{d.get('handle')}" if d.get('handle') else None,
        'admin': f"https://admin.shopify.com/store/designer-laboratory-sandbox/products/{d['id']}",
        'date': m.get('created_at_shopify'),
        'date_label': 'created',
    })

counts = {}
for it in items:
    counts[it['queue']] = counts.get(it['queue'], 0) + 1

snap = {
    'generated_at': datetime.datetime.now(datetime.timezone.utc).isoformat(),
    'sources': {
        'jstevens': 'dw_unified.tk10484_settlement_hold (Mac2) + jeffrey_stevens_catalog',
        'audit': 'dw-settlement-audit/verdicts.jsonl (binding sha 9197824e...)',
    },
    'counts': counts,
    'items': items,
}
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, 'w') as f:
    json.dump(snap, f, default=str)
print(json.dumps(counts), len(items), 'items ->', os.path.relpath(OUT))