|
| 1 | +# Case study: a real channel's publishing pipeline |
| 2 | + |
| 3 | +postpeer-pilot was not designed on a whiteboard — it was extracted from the daily |
| 4 | +pipeline of a real short-form video channel (four platforms: TikTok, Instagram, |
| 5 | +Facebook, YouTube; anonymized here). This page records the problem, the decisions, |
| 6 | +and what actually happened in operation. |
| 7 | + |
| 8 | +## The starting point |
| 9 | + |
| 10 | +The channel produces 2–4 videos per day through an automated render pipeline. |
| 11 | +Publishing was the bottleneck and the risk zone: |
| 12 | + |
| 13 | +- **Manual slot arithmetic.** Every batch meant reading the existing schedule, |
| 14 | + remembering the posting plan (which weekdays take how many posts), spotting |
| 15 | + collisions, and typing exact RFC3339 timestamps into an API call. 10–15 minutes |
| 16 | + per batch, every day, with a person in the loop who just wanted to say |
| 17 | + *"these three can go out"*. |
| 18 | +- **A plan based on a one-off analysis.** A 107-video analysis had produced a |
| 19 | + clear result: mornings win on every weekday, Tuesday is the strongest day, |
| 20 | + Friday/Saturday are dead, and flooding (8 posts/day) crashes per-video reach. |
| 21 | + That produced a fixed weekly plan — Mon–Wed 4, Thu/Sun 3, Fri/Sat 2, morning |
| 22 | + slots only. But the plan was a snapshot: nothing would ever update it as the |
| 23 | + channel changed. |
| 24 | +- **Series risk.** A 25-part series was ready to ship. Naively scheduled, it would |
| 25 | + have filled entire weeks and buried every other format — the flooding failure |
| 26 | + mode again, in a new shape. |
| 27 | + |
| 28 | +## Requirements as they emerged |
| 29 | + |
| 30 | +1. Drop finished videos into the pipeline; slot selection must be automatic and |
| 31 | + plan-aware (not "next morning hour", but "next slot the plan allows that isn't |
| 32 | + taken"). |
| 33 | +2. The plan should follow real performance — but a single viral video must never |
| 34 | + rewrite it. The operator's phrasing: *"adapt only on a bigger delta, say the |
| 35 | + last 4–8 weeks."* That sentence became the damping design. |
| 36 | +3. Never post live. Everything scheduled, everything reversible. |
| 37 | +4. Cap same-series posts per day (the 25-part series became `series_day_cap`). |
| 38 | +5. Refuse rather than guess: thin data, ambiguous matches, missing captions are |
| 39 | + all reasons to stop, not to improvise. |
| 40 | + |
| 41 | +## What happened in operation |
| 42 | + |
| 43 | +Numbers from the live channel (as of 2026-07-16): |
| 44 | + |
| 45 | +- **85 posts published** via the API pipeline, **49 scheduled ahead** — roughly |
| 46 | + two weeks of runway maintained continuously. |
| 47 | +- **117 tracked publishing events** in the channel's ledger since the pipeline |
| 48 | + went live (~4.5 weeks), across all four platforms. |
| 49 | +- **Zero accidental live posts.** The tool has no live path; the number is boring |
| 50 | + by construction, which is the point. |
| 51 | +- The 25-part series shipped over ~10 days at max 2/day next to the regular |
| 52 | + formats, instead of flooding the schedule. |
| 53 | +- **The planner's first real review proposed a change and refused to apply it** — |
| 54 | + the channel's history was younger than the long window, so the recent and prior |
| 55 | + windows would have been the same posts. The refusal reason was printed, the plan |
| 56 | + stayed, and the review is simply re-run as history accumulates. This is the |
| 57 | + damping working as designed: the interesting output was the *documented |
| 58 | + non-action*. |
| 59 | +- Slot planning time went from 10–15 minutes of manual schedule-reading per batch |
| 60 | + to a one-line request ("schedule these three"). |
| 61 | + |
| 62 | +## What operation taught us (fed back into the design) |
| 63 | + |
| 64 | +- **The API's edges bite silently.** `limit=101` returning an empty *success* |
| 65 | + response cost a debugging session — the latest scheduled posts just vanished |
| 66 | + from view. That's why pagination is mandatory in `api.py` and documented in the |
| 67 | + README. |
| 68 | +- **Text matching is a liability.** Reconciling published posts with performance |
| 69 | + data by caption similarity mostly works — until two posts in the same series |
| 70 | + differ by one word. That produced the ID-first ledger design; fuzzy matching |
| 71 | + survives only as a fallback for pre-tool posts, and ambiguity now returns |
| 72 | + "no data" instead of a guess. |
| 73 | +- **Every safety rule here was a real incident or a near-miss first.** The series |
| 74 | + cap exists because a 25-part series nearly flooded the plan. The freshness |
| 75 | + cutoff exists because a two-day-old post looks like a flop before its views |
| 76 | + mature. The double-count guard on the series cap was found by the demo script |
| 77 | + in this repo, then pinned by a test. |
| 78 | + |
| 79 | +## Next iteration |
| 80 | + |
| 81 | +- Feed per-platform totals into the planner as platforms diverge (the config |
| 82 | + already supports `planner_sources`). |
| 83 | +- Revisit the damping thresholds once the channel has ≥ 16 weeks of history — |
| 84 | + the backtest harness exists precisely so that change is a measurement, not an |
| 85 | + opinion. |
0 commit comments