DEV Community

Walker Brown
Walker Brown

Posted on

I read the same 298 Shopify catalogues again a month later

On 30 September I posted I read 298 Shopify catalogues to find out how often a size run has a hole in it.
The figure it led with was 190 of 298: that many catalogues had at least one
style whose core sizes, the middle half of the run, were all sold out while two
or more other sizes were still on sale. I call that a stranded run.

The question

The obvious objection is the one I would have made myself. The reads were taken
between 14 and 19 September, the end of a season. A broken run at season's end
is just clearance, and clearance is the plan, not a problem.

That objection can be tested. If it is the whole story, a month later each run
has gone the way clearance goes: sold out, or taken down. A run that is still on
sale, with its core still gone and its ends still listed, is not clearing. So on
19 October I read the same 298 catalogues again, with the same scanner and the
same page depth, 30 to 35 days after the first reads, and followed every
stranded run behind the figure.

The problem: September kept nothing to match on

The September scanner kept only what it needed to judge a run: title, handle,
product type, option names, and per variant the option values and the
available flag. No product id, no variant id, no price. I had not planned a
second reading, and that shapes everything below.

So each run is found again by two keys:

  • the product's handle, the store's own URL key for the product, unique within the store;
  • the run's key, the colourway plus a length modifier where there is one, because M and M Long are two different runs.

Never by title. A matched run is comparable only if it lists exactly the same
sizes in the same order as before. If a size was added or dropped, the core
moved.

What a match proves: the same address on the same store still sells that
colourway with the same size list, and here is what can be bought now. What it
cannot prove: that it is the same stock, or that the price has not changed. And
a miss proves less than it looks, because a renamed handle is indistinguishable
from a deleted product.

The read cap matters too. /products.json stops at 1,000 products, newest
first, so a product missing from a capped read may still be on the store, pushed
out of the window. 44 catalogues were capped in September and 41 now. A missing
product is called gone only when both reads were whole.

Before anything is compared, the script recounts September's stranded runs from
the cached reads and refuses to print a figure unless the recount matches the
published per-store number for every store.

The classes

Each run lands in exactly one class, checked in this order. The function is
taken whole from persistence.py; runs_for_product and core_span are the
same ones the September figures used.

def classify(sept_run, key, now_product, sept_whole, now_whole, now_read=True):
    """One September-stranded run against what the store shows now."""
    if not now_read:
        return "store not read"
    if now_product is None:
        return "product gone" if (sept_whole and now_whole) else "not seen"
    runs = dict(runs_for_product(now_product))
    run = runs.get(key)
    if run is None or [p for p, _, _ in run] != [p for p, _, _ in sept_run]:
        return "run changed"
    avail = [a for _, _, a in run]
    if not any(avail):
        return "sold out"
    s, e = core_span(len(run))
    if any(avail[s:e]):
        return "core restocked"
    return "still stranded" if sum(avail) >= 2 else "broken, no longer stranded"
Enter fullscreen mode Exit fullscreen mode

September's figure counts one stranded run per style, 1,581 in all. This is
where each of them went:

class runs of 1,581
still stranded 864 54.6%
broken, no longer stranded (core gone, one size left) 185 11.7%
core restocked 178 11.3%
sold out 52 3.3%
run changed (different sizes listed) 15 0.9%
product gone (missing from two whole reads) 112 7.1%
not seen (missing, but a read was capped) 175 11.1%
store not read 0 0.0%

The result

The first four rows are the 1,279 runs that list the same sizes both times.
Of those, 864 of 1,279 (67.6%) are still stranded, and 1,049 (82.0%) are
still on sale with the core gone. Counted against all 1,581, including the runs
that cannot be compared, still stranded is 54.6%.

By catalogue: 190 had a stranded style in September, all 190 were read again,
and 156 of 190 (82.1%) still have one of the same runs stranded.

The other direction is how fast new runs strand. Over every colourway present
both times with the same sizes, not only one per style, 469 of the 63,989 runs
that were not stranded in September are stranded now: 0.7%. On that wider
count, 933 of 1,374 September runs (67.9%) are still stranded, which agrees with
the one-per-style figure. At the store level, 185 of the 296 catalogues read
again (62.5%) have a stranded style now, against 63.8% in September.

The level barely moved, and it held mostly because the same runs stayed stranded.

The caveats

  • No prices. September kept none, so this says nothing about markdowns. It shows these runs had not cleared in a month, not that nobody is trying to clear them.
  • A renamed product drops out. A handle changed since September reads as product gone or not seen, never as still stranded.
  • "Available" is the store's own flag. It says a size can be bought, not how many are left, and a store that sells on backorder reads as in stock throughout.
  • Early stops. September reads that stopped part-way were not marked, so a September read under 1,000 was taken as whole. The October scanner marks them; none stopped early.
  • A convenience sample. It is the stores I could reach, not Shopify apparel as a whole.
  • Two stores were not read again. One now disallows /products.json in its robots.txt, and one was not read. Neither is among the 190.

Links

The full section is on the study page:
a month later.
The first article, with the method, is
here.

The study is by the maker of Sizecurve, a Shopify app.

Top comments (0)