05/AI & Automation

SeasonalCatalogMediaPipeline

~1,000 images, 165 products, and no click-by-click afternoon

Year
2026
Status
Internal tooling
Category
AI & Automation
Stack
Python 3.14 · Standard library only · WordPress REST API

01/Context

A first attempt to automate this through a hosted workflow tool failed for an infrastructure reason recorded in the code: the site's shared host blocks the automation vendor's cloud IP ranges. The pipeline had to be rebuilt to run from a local machine.

WordPress also cannot replace a media file's bytes in place. Refreshing a re-exported image is a delete-then-upload — a destructive operation nobody wanted to run blind across a thousand files on a flaky shared host.

02/At a glance

Problem
A seasonal drop meant getting roughly 1,000 images into WordPress under a strict naming convention, then telling each of 165 products which images are its own, in which order, with which colour label. Source filenames come from a design export and do not match the catalog's colourway names — colours appear in a different order, get abbreviated, and style names contain typos, so a naive string match silently binds the wrong swatch to the wrong colourway.
Built
Seven standalone Python CLI tools, standard library only, forming a staged pipeline from a design export to live collection pages — every writing tool dry-run by default.
Role
Sole developer and operator. Designed the naming convention, wrote the matching heuristics and the WordPress client, and ran the pipeline against production.
Changed
A drop that would otherwise be a click-by-click afternoon in the media library and ACF editor became a reviewable plan you approve and apply.

03/Architecture

Seven single-purpose CLIs over one shared matcher and one catalog CSV. Every tool that writes defaults to a dry run and requires an explicit --apply; destructive actions require two flags.

Seasonal Catalog Media Pipeline architecture diagramData flows through 13 components: Design export (Source), Catalog CSV (Source), Filename matcher (Decision logic), rename_swatches (Decision logic), Reconciliation tools (Decision logic), photos_sync (Service), media_refresh (Service), ws_link (Service), Collection pages (Interface), WordPress REST (External), Audit reports + log (Output), Media library (Store), Product ACF fields (Store). Connections: Design export to rename_swatches; Design export to photos_sync; Design export to media_refresh; Catalog CSV to Filename matcher; Filename matcher to photos_sync; Filename matcher to media_refresh; rename_swatches to media_refresh; Catalog CSV to ws_link; photos_sync to WordPress REST; media_refresh to WordPress REST; ws_link to WordPress REST; Reconciliation tools to WordPress REST; WordPress REST to Media library; WordPress REST to Product ACF fields; Media library to ws_link; Product ACF fields to Collection pages; Media library to Collection pages; Reconciliation tools to Audit reports + log; media_refresh to Audit reports + log.Design export~527 swatches + ~520 photosCatalog CSV165 products · 517 colourwa…Filename matcherToken matching, typo-tolera…rename_swatchesExport naming → site conven…Reconciliation toolsRead-only; opt-in dupe dele…photos_syncUploads only what is missingmedia_refreshSize diff → delete + re-upl…ws_linkBitmask-DP swatch assignmentCollection pagesRenders swatches by field o…WordPress RESTApp password · throttle + b…Audit reports + logMedia libraryProduct ACF fieldsswatch_1..10 image/name pai…

04/The system

Typo-tolerant matching, human-reviewed

A shared matcher uses token sequence and set matching with yarn-type and word-order tiebreaks to map a design-export filename onto a product and colourway from the catalog CSV.

It is deliberately not trusted blindly: every run prints its plan, and reviewing the matches is a documented operator step. The tool is confident enough to be useful and honest enough to be checked.

Delete-then-upload, one file at a time

The refresh tool compares local file sizes against the media library's recorded sizes and classifies every file as NEW, CHANGED or SAME. On apply, it deletes and re-uploads one file at a time, so a product is never imageless for more than a couple of seconds.

An interrupted refresh can simply be re-invoked; uploads skip what already exists.

Binding swatches in the catalog's own order

The linking tool writes the ACF payload for each product — style, yarn, code, price, collection, section, sort order, plus ten swatch image and name pairs in the catalog's own colour order — and picks the featured image to match swatch one.

Assignment uses a bitmask dynamic-programming pass so the whole set of swatches for a product is solved together rather than greedily one at a time.

Reconciliation as an artifact

Three read-only tools reconcile the live site against the catalog and write duplicate, missing and extra reports to disk. Those files are the record of what still needs a human — the pipeline's output includes its own to-do list.

05/What was hard

A destructive operation on an unreliable host

Delete-then-upload across a thousand files, on shared hosting, with no transaction. The mitigations are unglamorous and they are the whole project: classify before acting, act one file at a time, make re-running safe, and log every operation to an append-only file.

Names that almost match

Abbreviated colours, reordered words and typos in style names mean exact matching fails and fuzzy matching binds the wrong swatch. Token-based matching with tiebreaks got the hit rate high enough to be useful, and a mandatory dry-run review handles the rest.

Writing tools someone else can operate

Every script opens with a runbook in second person — what must be next to it, the exact commands for dry-run and full runs, what happens to odd cases, and what to run next. They were written to be handed over, not to be run only by their author.

06/Automation

Replaces renaming roughly 527 swatch files by hand, dragging around 1,000 files into the media library, and opening each of 165 product records to attach up to ten swatch images and ten colour labels in the right order. Deliberately left manual: the export, reviewing every dry-run plan, reviewing typo-tolerant matches, and deciding what to do with extras.

08/Direction & delivery

Safety-by-default applied across the whole toolkit rather than per script, with destructive actions gated behind two flags. Operational discipline shows in the retained artifacts: an append-only timestamped run log, and three audit output files kept in the working directory as the record of what still needs attention.

09/Scale

7
CLI tools
1,852
Lines of Python
0
Third-party dependencies
165
Products bound
517
Colourways

Objective size signals taken from the repository. No impact metrics are claimed that the source does not prove.

10/What I would tell the next person

  • Dry-run by default is not a nicety on a pipeline with delete semantics. It is the product.
  • Write the docstring as a runbook for the person who is not you.
  • When infrastructure blocks the elegant approach — a vendor's IP ranges blocked by shared hosting — rebuild rather than fight it.

11/Technologies

  • Python 3.14
  • Standard library only
  • WordPress REST API
  • Advanced Custom Fields
  • Application Passwords
  • CSV
  • WebP / PNG

Integrations

  • WordPress REST (media listing, upload, force-delete, ACF writes)
  • Advanced Custom Fields via the product endpoint

Runs against a password-protected wholesale catalog. Product imagery is an unreleased season and is not shown.