Interactive demo · synthetic dataEvery record on this page is fabricated.

Seasonal Catalog Media Pipeline

Eight command-line tools that move a season’s images onto a wholesale catalogue: the shell simulated, the matching real.

The production code behind this demo is my own work — design, implementation, the naming convention and the dry-run-by-default rule, the season run and its runbooks.

Read the case study

PICK A TOOL

Map exported swatch filenames onto the loader's convention. Anything it cannot place is left alone.

FLAGS
$ python rename_swatches.py ./M27

PICK A TOOL, CHOOSE FLAGS, PRESS RUN

THE EIGHT TOOLS, AND WHICH OF THEM CAN WRITE

The blast radius is the architecture. One tool that did all of this would have to be trusted all at once; eight of them can be trusted separately, and three of them cannot do damage at all.

  • rename_swatches.py · DRY-RUN BY DEFAULT

    renames exported swatch files to the loader's convention

  • photos_sync.py · DRY-RUN BY DEFAULT

    matches colourway photos, uploads only what is missing

  • ws_link.py · DRY-RUN BY DEFAULT

    assigns swatches to colourways and PATCHes the ACF fields

  • find_missing.py · READ-ONLY BY CONSTRUCTION

    which sw-* files never made it into the media library

  • photo_audit.py · READ-ONLY BY CONSTRUCTION

    colourways with no photo, photos with no colourway

  • inspect_media.py · READ-ONLY BY CONSTRUCTION

    duplicate -1 / -2 slugs from an accidental double upload

  • media_refresh.py · DRY-RUN BY DEFAULT

    delete-then-upload, one file at a time

  • probe_upload.py · WRITES ITS OWN TESTS, THEN DELETES THEM

    finds the host's real upload ceiling, then removes every test it made

What is real, and what is simulated

Runs in your browser

  • The order-insensitive token set-match for colourways and the ordered seq-match for product names, which accepts an abbreviation as a prefix in either direction — 'CR' in a filename satisfies 'CREW'
  • The one-character-tolerant typo fallback, and the two-character miss it correctly refuses
  • The score matrix: normalised whole-label and per-segment containment, with the 0.85 penalty on any segment but the first
  • Both assignment algorithms — greedy per colourway, and the global best-total bitmask dynamic program with its greedy fallback above sixteen swatches
  • The 0.25 floor, the leftover de-duplication rule, and the file-size diff that decides NEW / CHANGED / SAME

Ported by hand from the Python, function by function, against the behaviour the source's own comments claim. 56 tests assert it, including the one that matters: over a single score matrix the greedy pass strands a colourway and the dynamic program does not.

Simulated

  • The shell itself. Nothing on this page executes a process; the terminal replays a stage script at a readable pace
  • WordPress REST and ACF, every upload, every delete, the Pillow downscale and the host's size rejection
  • The catalogue: twelve products, twenty-three filenames, five swatch tiles and every media id are invented for this page. The tiles are flat colours drawn as SVG — there is no product photography here