The Second Quality Layer: What Rules Can't Handle Goes to a 10-Minute Human Checklist

Automation only handles rules; judgment must stay human: a 10-minute review checklist per 40 assets beats any upload script.
A developer let a pipeline auto-upload 40 vector assets to Adobe Stock, and 11 were rejected. The reason wasn't CSV formatting or upload failure, but titles that looked like filenames, keywords unrelated to the image, and visible defects—none of which code can judge. The article argues automation excels at the rule layer: header columns, duplicate-upload tracking, file packaging all get solved 100% by scripts. But the second quality layer (does the asset look right, is the description accurate) can't be fully automated: LLMs only flag obvious failures, and calling APIs at scale is expensive. The effective approach is hybrid: 60 dpi low-resolution pre-checks catch mechanical failures, while a human uses a 4-question checklist to review 40 assets in 10 minutes. The folder structure (assets/ to output/) forms a physical gate that prevents unreviewed assets from entering packaging. The core principle isn't automate less—it's automate the right layer.
A developer who bulk-uploads digital assets once set a pipeline to automatically upload 40 vector assets to Adobe Stock at 2 AM. The CSV headers were correct, the upload finished on time, and he slept well. When he woke up at noon, 11 assets had been rejected. The reason wasn't formatting: some titles looked like filenames, some keywords had nothing to do with the image, and one file's layer groups had been flattened during export, leaving a visible seam. Automation did what it was supposed to do perfectly, but the part it was responsible for was too small.

Automation Handles Rules, Not Judgment
The developer's make_metadata.py script can output exactly the number of columns each platform requires: Adobe Stock needs 5 columns, Vecteezy needs 4, Dreamstime needs 15. If the headers are wrong, the upload fails outright before any human review. These kinds of rule-based problems the script solves 100%. upload_tracker.py records which assets have already been submitted, preventing a batch from being run twice—duplicate uploads are one of the fastest ways to get an account flagged. Rules are enforced by code, with no exceptions.
The problem is that those 11 rejection letters weren't saying "wrong number of columns." Adobe's reviewers were saying: the content doesn't match the metadata, or the asset itself has quality issues. This isn't something a CSV format check can catch. This is the core problem of the second quality layer: does the asset actually look right, and does the description actually describe it?
Why the Second Layer Can't Be Fully Automated
The developer's main pipeline is about 19,000 lines of Python code, and an early chunk of it tried to make code automatically judge asset quality. The honest result: an LLM can flag obvious failures—blank output, truncated rendering, duplicate keyword lists—but it can't reliably judge whether a sticker design is legible at sticker size, or whether a template's spacing feels awkward. And running these checks at scale costs real money, since every AI-assisted review consumes API credits. For a pipeline producing hundreds of assets, the cost of "letting the model look at everything" adds up fast.
What actually works is a hybrid model, roughly divided like this:
- Automated pre-checks catch mechanical failures. Render each asset at a low preview resolution of about 60 dpi instead of 300 dpi, checking for blank areas, zero-byte files, and dimension errors. Checking at 60 dpi processes about 25x fewer pixels than 300 dpi—a crucial difference when the server only has 1 vCPU and 2 GB of memory.
- A human pass catches everything else, done in batches with a checklist. 10 minutes per batch of 40 assets. Not 10 minutes per asset, but per batch.
The checklist is short and specific: Does the title describe the subject in plain words? Are the keywords things actually visible in the image? Are there visible defects at 100% zoom? Does the file open cleanly? Just those four questions, and they're enough to catch all 11 rejected assets.
The limitations of LLM asset review stem from both cost and capability constraints. Current mainstream vision-language models (such as GPT-4V, Claude 3 Vision) cost roughly a few cents to a few tens of cents per image to run inference on, depending on resolution and token count. For a pipeline producing hundreds of assets, if every asset requires uploading a high-resolution image and asking the model to judge it, the cost per batch could exceed the expected revenue from the assets themselves. Moreover, models have systematic biases in judging "design quality": they're good at identifying "is there content" (blankness, truncation, garbled text) but bad at evaluating "is the design good-looking" (alignment, spacing, visual weight balance). This bias comes from training data—models have seen massive numbers of images but are rarely annotated with subjective judgments like "this poster's whitespace is unbalanced." So treating an LLM as a quality gatekeeper is essentially spending big money on a sieve that only catches obvious errors, far less cost-effective than low-resolution pre-checks plus a human checklist.
The Batch Review Habit Matters More Than the Tool Itself
The human gate works because it happens before upload, for the entire batch, rather than after rejection, patching files one by one. The rejection loop takes days: Adobe's review isn't instant, and every rejected asset means a zero-revenue slot in that cycle. Catching 11 bad assets with 10 minutes of review is worth more than any upload automation—because upload was never the bottleneck, judgment is.
At the operational level, the folder structure physically enforces this gate: raw output goes into assets/, reviewed and corrected files go into output/, and the metadata CSV is built from output/, never from assets/. The pipeline physically cannot package an asset that hasn't gone through the review step, because the review step is the act of moving the file between folders. This is a gate made of directory structure rather than code, and it holds up better than any validation script ever written.
The hidden cost of a rejected asset far exceeds the file itself. Adobe Stock's review cycle is typically 1–3 business days, and after rejection you need to revise, re-upload, and wait for review again. This means an asset rejected on Monday can't re-enter the review queue until Thursday at the earliest. If that asset was originally slotted into this week's upload quota, that slot is permanently wasted—the platform doesn't compensate for slots taken up by rejections. More seriously, there's account reputation: repeated rejections can lead to reduced upload quotas or even account suspension. So the value of "10 minutes to review 40 assets before upload" isn't just saving a few days of turnaround time, but protecting the account's long-term upload privileges. This math becomes especially obvious at scale: for an account uploading 40 assets a day, a single batch rejection could cost a week of upload capacity.
What Automation Should Actually Take Over
Once the second quality layer signs off, automation takes over completely, and does it well. Packaging a reviewed product is a solved problem: compress files, use Pillow to render a 1280x720 cover and 600x600 thumbnail, use reportlab to write a styled PDF. pack_product.py does it in one command, and md2pdf.py turns documents into sellable PDFs. All of this runs on the same 1 vCPU, 2 GB server, one process at a time, calling gc.collect() between items, using ru_maxrss to measure actual peak memory rather than guessing. A full run produces 39 finished products, about 856 MB of output, at a hardware cost of a few dollars a month.
The lesson from those 11 rejected assets isn't "automate less," but "automate the right layer." Rules and packaging: fully automated, zero regrets. Visual and semantic judgment: a cheap, batched, human checklist enforced by folder structure. Trying to stuff judgment into code is where he lost a week and 11 assets; the fix costs 10 minutes per batch.
Running image processing on a low-memory server requires an active memory management strategy. On a 1 vCPU / 2 GB instance, when rendering 300 dpi previews or generating PDFs, Pillow and reportlab keep a lot of temporary objects in memory. Without explicitly calling gc.collect(), Python's cycle-reference garbage collection may not release these objects in time, causing peak memory to exceed the 2 GB limit and get terminated by the OOM Killer. The ru_maxrss mentioned in the original (obtained via resource.getrusage) is a reliable metric for measuring a process's peak physical memory on Linux, better suited than ps's instantaneous value for evaluating the memory needs of batch tasks. One process at a time, with forced collection between items, is a low-cost way to get predictable behavior on limited hardware.
An Underrated Engineering Principle
The real value of this story isn't in which specific script or which specific checklist, but in a clear-eyed awareness of where automation's boundaries lie. Many people building digital products focus their energy on expanding automation's coverage, but overlook this: once an asset is rejected by a platform, what's lost isn't just that one file, but the entire cycle's upload quota, review wait time, and account reputation. Judgment is the only part that can't be replaced by an API call, and it happens to take only 10 minutes per batch.
If you were to productize this approach, the core is four scripts (metadata generation with exact headers, upload tracking, document-to-PDF conversion, product packaging), a folder structure that serves as the review gate, and a full record of running it all on a 1 vCPU / 2 GB machine. There are only two dependencies, and the metadata and tracking scripts need nothing but Python itself.
Related articles

Build an AI Super Employee System at Zero Cost: A Full Breakdown of Automated Agent Workflows
A deep breakdown of the AI super employee system demoed on Bilibili: agent orchestration, digital human voiceovers, map and enterprise lead generation, 24/7 customer service, and legal modules. We analyze the business logic and compliance risks behind zero-cost signup to help entrepreneurs judge the real ROI of this AI digital employee system.

The Essential Difference Between Chatbots and AI Agents: Crossing from Conversation to Execution
What's the difference between a chatbot and an AI agent? Based on the MCP series on the Learn Tech channel, this article uses examples like booking flights and summarizing notes to explain: a chatbot only gives answers, while an agent actually completes tasks through a think-act-observe loop. It breaks down the four core components—model, tools, memory, and goal—as well as MCP's role as a bridge, and discusses agents' applicable boundaries and permission risks.

Free GitHub Repo: 342 Hours of AI Courses, from Math to MCP Servers
A free GitHub repo offers 342 hours of AI courses: 523 lessons across 20 phases, from math fundamentals to agent building and MCP servers. Nearly 100,000 learners have started.