Vibecode 1of10
track this build4 phases, 12 steps, beginner friendly0%The core math is embarrassingly simple: pull a channel's uploads, compute a median or trailing baseline, divide each video's views by it, sort descending. The YouTube Data API hands you everything you need for that, and an agent can wire up a local outlier dashboard for a list of channels you care about in one sitting. Where it breaks down is scale: 1of10's real product is a pre-indexed corpus of millions of videos you can search across niches you have never heard of, with history that predates your interest. Your build can only see channels you thought to track, and the free API quota caps how many you can refresh per day. Good enough for watching 50 competitors, useless for open-ended idea mining.
You are building a lean indie version of 1of10. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # 1of10 · indie build A research tool that finds videos performing far above their own channel's normal range, where the engineering is entirely shaped by an API quota that makes search unaffordable and cheap list operations the only viable path. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Collection | Playlist and video list operations, never search | search costs a hundred times what a list costs and the allowance is fixed | | Caching | Aggressive local storage of everything fetched | the quota resets daily and refetching what you already have wastes it | | Statistics | Comparison against a channel's own distribution, age adjusted | a channel mean is a bad baseline for a heavily skewed quantity | | Scheduling | A daily budget spent deliberately across tracked channels | spending the allowance by lunchtime means no data for the rest of the day | | Runtime | Python 3.12 with SQLite | the work is fetching, storing and computing | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs collection and the statistics. - Get it: python.org or your package manager, then a virtual environment. - Verify: python3 --version prints 3.12 or higher - [ ] **A YouTube Data API key with its quota understood** · free within the allowance; extensions require an audit - Why: The whole architecture follows from the allowance, and building before reading the quota costs a rewrite rather than a tuning pass. - Get it: Create a project, enable the API, and read the quota cost table before writing the fetch layer. - Verify: A list request and a search request are both issued and their unit costs confirmed against the documentation - [ ] **Fifty channels you actually want to track** · free - Why: The build is designed around tracking a chosen set rather than searching, so the set has to exist before the collection schedule makes sense. - Get it: List channels in your area of interest, including at least one with very few uploads and one with several hundred. - Verify: Fifty channel identifiers are recorded including a small and a large catalogue - [ ] **Two weeks of collected snapshots** · free, and it takes two weeks - Why: Velocity, which is the difference between a genuinely interesting video and an old one, requires a series rather than a single reading. - Get it: Run collection daily for two weeks before evaluating the outlier logic. - Verify: At least fourteen snapshots exist per tracked video - [ ] **Your outlier definition, written down** · free - Why: A multiple of the median, a percentile and an age-adjusted percentile give different answers, and picking one afterwards means picking whichever produces the results you liked. - Get it: Decide the definition and the minimum catalogue size before running it against real channels. - Verify: A written definition exists including the minimum catalogue size ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. - Cross-channel discovery: you can only find outliers in channels you already listed - Historical depth, their index has view curves from before you started collecting - Quota headroom, refreshing thousands of channels daily needs paid access or many keys - Curated niche collections, thumbnail galleries and saved-idea workflows - Shorts vs long-form normalization and view-velocity nuance that took them iterations to get right If one of those is essential to you, that is the reason to keep paying for 1of10, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · 1of10 The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me finding videos that massively outperformed their channel's own baseline to replace 1of10. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The quota decides the architecture here, so read it before designing anything. The YouTube Data API gives 10,000 units a day per project. A search request costs 100 units, so the free allowance is about a hundred searches a day. A list request costs one unit, so the same allowance is thousands of lookups if you already know what to look up. You cannot buy more: the only route to a higher limit is an audit and extension request that takes weeks and is frequently refused. That means a tool built around searching for interesting videos does not work. A tool built around tracking channels you have chosen, fetching their uploads cheaply and computing outliers locally works comfortably inside the free allowance. Those are different products and the difference is forced by the quota rather than chosen. The second thing to get right is the statistics. A video with ten times the views of its channel's average is not necessarily interesting, because view counts are heavily skewed and an average is the wrong baseline. Compare against the channel's own distribution and against video age, since a two year old video has had two years to accumulate. ### Stack (fixed, do not substitute) - Collection: Playlist and video list operations, never search · search costs a hundred times what a list costs and the allowance is fixed - Caching: Aggressive local storage of everything fetched · the quota resets daily and refetching what you already have wastes it - Statistics: Comparison against a channel's own distribution, age adjusted · a channel mean is a bad baseline for a heavily skewed quantity - Scheduling: A daily budget spent deliberately across tracked channels · spending the allowance by lunchtime means no data for the rest of the day - Runtime: Python 3.12 with SQLite · the work is fetching, storing and computing ### Data model (create this before Phase 1) - channels(id, external_id, title, subscriber_count, tracked_since, uploads_playlist_id) - videos(id, channel_id, external_id, title, published_at, duration_seconds, first_seen_at) - snapshots(video_id, at, views, likes, comments) · a series, because velocity matters more than a total - baselines(channel_id, computed_at, median_views, quantiles_json, age_curve_json) · the channel's own distribution - outliers(video_id, computed_at, ratio_to_median, age_adjusted_ratio, percentile, confidence) - quota_ledger(day, operation, units, cumulative) · what the allowance was spent on - A snapshot series is kept rather than only the latest count, because a video reaching a million views in a week and one reaching it over three years are entirely different findings. ### Phase 1 · Spending the allowance deliberately Build: A quota ledger recording the unit cost of every request before it is made. Refuse a request that would exceed the daily allowance. Prefer list operations and make search either unavailable or explicitly rationed. Trap: A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. Done when: Every request records its unit cost before being issued and the ledger reconciles against the provider's own reported usage. A request that would exceed the daily allowance is refused rather than attempted. Search is either unavailable or explicitly rationed with its cost stated. Exhausting the allowance leaves the collected data consistent rather than partial. Do not build yet: No statistics, tracking or reporting. ### Phase 2 · Tracking channels cheaply Build: Fetch each channel's uploads through its playlist and retrieve statistics in batches. Cache everything and refetch only what could have changed. Handle a channel that disappears or makes videos private. Trap: Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. Done when: A hundred tracked channels update daily within the allowance, measured from the ledger. New uploads are fetched promptly while old videos are sampled on a decaying schedule. A deleted or private video is recorded as gone rather than dropped. Statistics are retrieved in batches rather than one call per video. Do not build yet: No baselines or outlier detection yet. ### Phase 3 · A baseline that is not the mean Build: Compute per-channel quantiles rather than a mean. Adjust for video age, since older videos have accumulated longer. Require a minimum number of videos before computing a baseline at all. Trap: Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. Done when: Baselines use quantiles rather than a mean, verified against a channel with one dominant video. Age adjustment is applied so a recent video is not penalised for having existed for a week. A channel with too few videos produces no baseline rather than an unreliable one. Recomputing a baseline is deterministic. Do not build yet: No outlier scoring or reporting yet. ### Phase 4 · Outliers with a confidence attached Build: Score videos against their channel's age-adjusted distribution, reporting a percentile and a confidence reflecting catalogue size and data recency. Use the snapshot series so velocity is distinguishable from accumulation. Trap: Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. Done when: Outlier scores carry a percentile and a confidence reflecting catalogue size and data age. A video that accumulated views slowly over years is distinguished from one that did so in a week, using the snapshot series. A channel with a small catalogue produces low-confidence results rather than none or overconfident ones. Scores are reproducible from stored snapshots. Do not build yet: No reporting or export yet. ### Phase 5 · Reporting without over-claiming Build: Report outliers with their statistics and links, filterable by channel, topic and recency. State plainly that the tool identifies unusual performance and does not explain it. Export the data with its snapshots so a finding can be rechecked. Trap: Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it. Done when: Reports show statistics and links with no causal explanation anywhere, verified by reading the output. Filters by channel, recency and confidence work. Export includes snapshots so a finding can be rechecked later. The quota ledger is visible so the day's remaining allowance is known. Do not build yet: Nothing after this. ### Out of scope (and why) - Search-driven discovery of channels you do not already know, which the quota makes impractical at any useful scale. - Explaining why a video performed well, which the data cannot support and which people will act on if you provide it. - Thumbnail and title analysis, which is where these tools make their claims and where the evidence is weakest. - Scraping the site instead of using the API, which is against the terms and is the obvious workaround for the quota. ### README must contain - That the daily allowance is fixed at ten thousand units, a search costs a hundred and a list costs one - That the quota cannot be bought and an extension requires an audit that is frequently refused - That baselines use quantiles rather than a mean, because one viral video ruins an average - That outlier scores carry a confidence derived from catalogue size and data recency - That the tool reports unusual performance and never explains it ===== AGENTS.md ===== # Agent instructions · 1of10 indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Playlist and video list operations, never search, Aggressive local storage of everything fetched, Comparison against a channel's own distribution, age adjusted, A daily budget spent deliberately across tracked channels, Python 3.12 with SQLite. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. ===== BUILD_PLAN.md ===== # Build plan · 1of10 A research tool that finds videos performing far above their own channel's normal range, where the engineering is entirely shaped by an API quota that makes search unaffordable and cheap list operations the only viable path. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Spending the allowance deliberately Every request is budgeted and the day's allowance is never exhausted by accident. ### Steps 1. Budget before every call Refusing is cheaper than a quota error halfway through a run. Files: `src/quota.py`, `tests/test_quota.py` 2. Prefer list over search One unit against a hundred. This decides what the product can be. Files: `src/fetch.py` 3. Reconcile the ledger against reported usage So your accounting matches theirs rather than diverging quietly. Files: `src/quota.py` ### Done when - [ ] Every request records its cost before issue - [ ] Over-budget requests are refused - [ ] Search is unavailable or explicitly rationed - [ ] Exhaustion leaves consistent data ### Watch out - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. ## Phase 2 · Tracking channels cheaply A hundred channels update daily inside the free allowance. ### Steps 1. Fetch uploads through the playlist One unit per page rather than a hundred per search. Files: `src/channels.py` 2. Batch statistics requests Fifty video identifiers per call rather than fifty calls. Files: `src/stats.py` 3. Sample old videos on a decaying schedule Their counts barely move and the allowance is better spent elsewhere. Files: `src/schedule.py` ### Done when - [ ] A hundred channels update daily within the allowance - [ ] New uploads are fetched promptly - [ ] Deleted videos are recorded as gone - [ ] Statistics are batched ### Watch out - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. ## Phase 3 · A baseline that is not the mean Each channel is compared against its own distribution rather than an average. ### Steps 1. Use quantiles, not the mean One viral video drags a mean above the whole catalogue. Files: `src/baseline.py`, `tests/test_baseline.py` 2. Adjust for age A week-old video has not had the time an old one has. Files: `src/age.py` 3. Refuse below a minimum catalogue size A baseline from six videos describes nothing. Files: `src/baseline.py` ### Done when - [ ] Baselines use quantiles rather than a mean - [ ] Age adjustment is applied - [ ] Small catalogues produce no baseline - [ ] Recomputation is deterministic ### Watch out - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. ## Phase 4 · Outliers with a confidence attached A flagged video is genuinely unusual for that channel rather than merely large. ### Steps 1. Score against the age-adjusted distribution Percentile, not a bare multiple of the median. Files: `src/outliers.py` 2. Attach a confidence Catalogue size and data recency. Fifteen videos is not four hundred. Files: `src/outliers.py` 3. Use the snapshot series for velocity A million views in a week is a different finding from over three years. Files: `src/velocity.py` ### Done when - [ ] Scores carry percentile and confidence - [ ] Velocity is distinguished from accumulation - [ ] Small catalogues give low confidence rather than false certainty - [ ] Scores reproduce from stored snapshots ### Watch out - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. ## Not in this build - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. ## After v1, if you want it - Alerting when a tracked channel publishes an outlier, which is the useful daily output - Cross-channel comparison within a topic, once baselines are trustworthy - Retention of removed videos as a record, since disappearance is itself information ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Channels, videos and the snapshot series. DB_PATH=./research.db # Required · secret. Subject to the daily allowance, which shapes the whole build. YOUTUBE_API_KEY=a project API key # Required. The allowance. Requests are budgeted against it before being issued. DAILY_QUOTA_UNITS=10000 # Required. A search costs a hundred units. Rationed or unavailable, never routine. ALLOW_SEARCH=false # Required. Below this no baseline is computed, because it would describe nothing. MIN_CATALOGUE_FOR_BASELINE=30 # Required. The series is the data. Keep it far longer than the latest reading. SNAPSHOT_RETENTION_DAYS=730
You are building a lean indie version of 1of10. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # 1of10 · indie build A research tool that finds videos performing far above their own channel's normal range, where the engineering is entirely shaped by an API quota that makes search unaffordable and cheap list operations the only viable path. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Collection | Playlist and video list operations, never search | search costs a hundred times what a list costs and the allowance is fixed | | Caching | Aggressive local storage of everything fetched | the quota resets daily and refetching what you already have wastes it | | Statistics | Comparison against a channel's own distribution, age adjusted | a channel mean is a bad baseline for a heavily skewed quantity | | Scheduling | A daily budget spent deliberately across tracked channels | spending the allowance by lunchtime means no data for the rest of the day | | Runtime | Python 3.12 with SQLite | the work is fetching, storing and computing | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs collection and the statistics. - Get it: python.org or your package manager, then a virtual environment. - Verify: python3 --version prints 3.12 or higher - [ ] **A YouTube Data API key with its quota understood** · free within the allowance; extensions require an audit - Why: The whole architecture follows from the allowance, and building before reading the quota costs a rewrite rather than a tuning pass. - Get it: Create a project, enable the API, and read the quota cost table before writing the fetch layer. - Verify: A list request and a search request are both issued and their unit costs confirmed against the documentation - [ ] **Fifty channels you actually want to track** · free - Why: The build is designed around tracking a chosen set rather than searching, so the set has to exist before the collection schedule makes sense. - Get it: List channels in your area of interest, including at least one with very few uploads and one with several hundred. - Verify: Fifty channel identifiers are recorded including a small and a large catalogue - [ ] **Two weeks of collected snapshots** · free, and it takes two weeks - Why: Velocity, which is the difference between a genuinely interesting video and an old one, requires a series rather than a single reading. - Get it: Run collection daily for two weeks before evaluating the outlier logic. - Verify: At least fourteen snapshots exist per tracked video - [ ] **Your outlier definition, written down** · free - Why: A multiple of the median, a percentile and an age-adjusted percentile give different answers, and picking one afterwards means picking whichever produces the results you liked. - Get it: Decide the definition and the minimum catalogue size before running it against real channels. - Verify: A written definition exists including the minimum catalogue size ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. - Cross-channel discovery: you can only find outliers in channels you already listed - Historical depth, their index has view curves from before you started collecting - Quota headroom, refreshing thousands of channels daily needs paid access or many keys - Curated niche collections, thumbnail galleries and saved-idea workflows - Shorts vs long-form normalization and view-velocity nuance that took them iterations to get right If one of those is essential to you, that is the reason to keep paying for 1of10, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · 1of10 The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me finding videos that massively outperformed their channel's own baseline to replace 1of10. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The quota decides the architecture here, so read it before designing anything. The YouTube Data API gives 10,000 units a day per project. A search request costs 100 units, so the free allowance is about a hundred searches a day. A list request costs one unit, so the same allowance is thousands of lookups if you already know what to look up. You cannot buy more: the only route to a higher limit is an audit and extension request that takes weeks and is frequently refused. That means a tool built around searching for interesting videos does not work. A tool built around tracking channels you have chosen, fetching their uploads cheaply and computing outliers locally works comfortably inside the free allowance. Those are different products and the difference is forced by the quota rather than chosen. The second thing to get right is the statistics. A video with ten times the views of its channel's average is not necessarily interesting, because view counts are heavily skewed and an average is the wrong baseline. Compare against the channel's own distribution and against video age, since a two year old video has had two years to accumulate. ### Stack (fixed, do not substitute) - Collection: Playlist and video list operations, never search · search costs a hundred times what a list costs and the allowance is fixed - Caching: Aggressive local storage of everything fetched · the quota resets daily and refetching what you already have wastes it - Statistics: Comparison against a channel's own distribution, age adjusted · a channel mean is a bad baseline for a heavily skewed quantity - Scheduling: A daily budget spent deliberately across tracked channels · spending the allowance by lunchtime means no data for the rest of the day - Runtime: Python 3.12 with SQLite · the work is fetching, storing and computing ### Data model (create this before Phase 1) - channels(id, external_id, title, subscriber_count, tracked_since, uploads_playlist_id) - videos(id, channel_id, external_id, title, published_at, duration_seconds, first_seen_at) - snapshots(video_id, at, views, likes, comments) · a series, because velocity matters more than a total - baselines(channel_id, computed_at, median_views, quantiles_json, age_curve_json) · the channel's own distribution - outliers(video_id, computed_at, ratio_to_median, age_adjusted_ratio, percentile, confidence) - quota_ledger(day, operation, units, cumulative) · what the allowance was spent on - A snapshot series is kept rather than only the latest count, because a video reaching a million views in a week and one reaching it over three years are entirely different findings. ### Phase 1 · Spending the allowance deliberately Build: A quota ledger recording the unit cost of every request before it is made. Refuse a request that would exceed the daily allowance. Prefer list operations and make search either unavailable or explicitly rationed. Trap: A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. Done when: Every request records its unit cost before being issued and the ledger reconciles against the provider's own reported usage. A request that would exceed the daily allowance is refused rather than attempted. Search is either unavailable or explicitly rationed with its cost stated. Exhausting the allowance leaves the collected data consistent rather than partial. Do not build yet: No statistics, tracking or reporting. ### Phase 2 · Tracking channels cheaply Build: Fetch each channel's uploads through its playlist and retrieve statistics in batches. Cache everything and refetch only what could have changed. Handle a channel that disappears or makes videos private. Trap: Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. Done when: A hundred tracked channels update daily within the allowance, measured from the ledger. New uploads are fetched promptly while old videos are sampled on a decaying schedule. A deleted or private video is recorded as gone rather than dropped. Statistics are retrieved in batches rather than one call per video. Do not build yet: No baselines or outlier detection yet. ### Phase 3 · A baseline that is not the mean Build: Compute per-channel quantiles rather than a mean. Adjust for video age, since older videos have accumulated longer. Require a minimum number of videos before computing a baseline at all. Trap: Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. Done when: Baselines use quantiles rather than a mean, verified against a channel with one dominant video. Age adjustment is applied so a recent video is not penalised for having existed for a week. A channel with too few videos produces no baseline rather than an unreliable one. Recomputing a baseline is deterministic. Do not build yet: No outlier scoring or reporting yet. ### Phase 4 · Outliers with a confidence attached Build: Score videos against their channel's age-adjusted distribution, reporting a percentile and a confidence reflecting catalogue size and data recency. Use the snapshot series so velocity is distinguishable from accumulation. Trap: Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. Done when: Outlier scores carry a percentile and a confidence reflecting catalogue size and data age. A video that accumulated views slowly over years is distinguished from one that did so in a week, using the snapshot series. A channel with a small catalogue produces low-confidence results rather than none or overconfident ones. Scores are reproducible from stored snapshots. Do not build yet: No reporting or export yet. ### Phase 5 · Reporting without over-claiming Build: Report outliers with their statistics and links, filterable by channel, topic and recency. State plainly that the tool identifies unusual performance and does not explain it. Export the data with its snapshots so a finding can be rechecked. Trap: Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it. Done when: Reports show statistics and links with no causal explanation anywhere, verified by reading the output. Filters by channel, recency and confidence work. Export includes snapshots so a finding can be rechecked later. The quota ledger is visible so the day's remaining allowance is known. Do not build yet: Nothing after this. ### Out of scope (and why) - Search-driven discovery of channels you do not already know, which the quota makes impractical at any useful scale. - Explaining why a video performed well, which the data cannot support and which people will act on if you provide it. - Thumbnail and title analysis, which is where these tools make their claims and where the evidence is weakest. - Scraping the site instead of using the API, which is against the terms and is the obvious workaround for the quota. ### README must contain - That the daily allowance is fixed at ten thousand units, a search costs a hundred and a list costs one - That the quota cannot be bought and an extension requires an audit that is frequently refused - That baselines use quantiles rather than a mean, because one viral video ruins an average - That outlier scores carry a confidence derived from catalogue size and data recency - That the tool reports unusual performance and never explains it ===== AGENTS.md ===== # Agent instructions · 1of10 indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Playlist and video list operations, never search, Aggressive local storage of everything fetched, Comparison against a channel's own distribution, age adjusted, A daily budget spent deliberately across tracked channels, Python 3.12 with SQLite. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. ===== BUILD_PLAN.md ===== # Build plan · 1of10 A research tool that finds videos performing far above their own channel's normal range, where the engineering is entirely shaped by an API quota that makes search unaffordable and cheap list operations the only viable path. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Spending the allowance deliberately Every request is budgeted and the day's allowance is never exhausted by accident. ### Steps 1. Budget before every call Refusing is cheaper than a quota error halfway through a run. Files: `src/quota.py`, `tests/test_quota.py` 2. Prefer list over search One unit against a hundred. This decides what the product can be. Files: `src/fetch.py` 3. Reconcile the ledger against reported usage So your accounting matches theirs rather than diverging quietly. Files: `src/quota.py` ### Done when - [ ] Every request records its cost before issue - [ ] Over-budget requests are refused - [ ] Search is unavailable or explicitly rationed - [ ] Exhaustion leaves consistent data ### Watch out - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. ## Phase 2 · Tracking channels cheaply A hundred channels update daily inside the free allowance. ### Steps 1. Fetch uploads through the playlist One unit per page rather than a hundred per search. Files: `src/channels.py` 2. Batch statistics requests Fifty video identifiers per call rather than fifty calls. Files: `src/stats.py` 3. Sample old videos on a decaying schedule Their counts barely move and the allowance is better spent elsewhere. Files: `src/schedule.py` ### Done when - [ ] A hundred channels update daily within the allowance - [ ] New uploads are fetched promptly - [ ] Deleted videos are recorded as gone - [ ] Statistics are batched ### Watch out - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. ## Phase 3 · A baseline that is not the mean Each channel is compared against its own distribution rather than an average. ### Steps 1. Use quantiles, not the mean One viral video drags a mean above the whole catalogue. Files: `src/baseline.py`, `tests/test_baseline.py` 2. Adjust for age A week-old video has not had the time an old one has. Files: `src/age.py` 3. Refuse below a minimum catalogue size A baseline from six videos describes nothing. Files: `src/baseline.py` ### Done when - [ ] Baselines use quantiles rather than a mean - [ ] Age adjustment is applied - [ ] Small catalogues produce no baseline - [ ] Recomputation is deterministic ### Watch out - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. ## Phase 4 · Outliers with a confidence attached A flagged video is genuinely unusual for that channel rather than merely large. ### Steps 1. Score against the age-adjusted distribution Percentile, not a bare multiple of the median. Files: `src/outliers.py` 2. Attach a confidence Catalogue size and data recency. Fifteen videos is not four hundred. Files: `src/outliers.py` 3. Use the snapshot series for velocity A million views in a week is a different finding from over three years. Files: `src/velocity.py` ### Done when - [ ] Scores carry percentile and confidence - [ ] Velocity is distinguished from accumulation - [ ] Small catalogues give low confidence rather than false certainty - [ ] Scores reproduce from stored snapshots ### Watch out - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. ## Not in this build - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. ## After v1, if you want it - Alerting when a tracked channel publishes an outlier, which is the useful daily output - Cross-channel comparison within a topic, once baselines are trustworthy - Retention of removed videos as a record, since disappearance is itself information ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Channels, videos and the snapshot series. DB_PATH=./research.db # Required · secret. Subject to the daily allowance, which shapes the whole build. YOUTUBE_API_KEY=a project API key # Required. The allowance. Requests are budgeted against it before being issued. DAILY_QUOTA_UNITS=10000 # Required. A search costs a hundred units. Rationed or unavailable, never routine. ALLOW_SEARCH=false # Required. Below this no baseline is computed, because it would describe nothing. MIN_CATALOGUE_FOR_BASELINE=30 # Required. The series is the data. Keep it far longer than the latest reading. SNAPSHOT_RETENTION_DAYS=730
You are building a production product version of 1of10. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # 1of10 · product brief ## Problem The core math is embarrassingly simple: pull a channel's uploads, compute a median or trailing baseline, divide each video's views by it, sort descending. The YouTube Data API hands you everything you need for that, and an agent can wire up a local outlier dashboard for a list of channels you care about in one sitting. Where it breaks down is scale: 1of10's real product is a pre-indexed corpus of millions of videos you can search across niches you have never heard of, with history that predates your interest. Your build can only see channels you thought to track, and the free API quota caps how many you can refresh per day. Good enough for watching 50 competitors, useless for open-ended idea mining. ## Product outcome A research tool that lives comfortably inside a fixed free allowance, compares each video against its own channel's distribution, and never pretends to know why something worked. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - YouTube Data API v3 key (free tier, 10k units/day) - Python 3.11 and a machine or cheap VPS to run a daily refresh - A hand-curated list of channels to track ## Explicit non-goals for v1 - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. - Cross-channel discovery: you can only find outliers in channels you already listed - Historical depth, their index has view curves from before you started collecting - Quota headroom, refreshing thousands of channels daily needs paid access or many keys - Curated niche collections, thumbnail galleries and saved-idea workflows - Shorts vs long-form normalization and view-velocity nuance that took them iterations to get right ## Success criteria - Every request is confirmed budgeted before issue - A hundred channels are confirmed to update within the allowance - Baselines are confirmed to use quantiles rather than a mean - Scores are confirmed to carry a confidence - Reports are confirmed to contain no causal explanation ===== BRIEF.md ===== # Build brief · 1of10 The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me finding videos that massively outperformed their channel's own baseline to replace 1of10. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The quota decides the architecture here, so read it before designing anything. The YouTube Data API gives 10,000 units a day per project. A search request costs 100 units, so the free allowance is about a hundred searches a day. A list request costs one unit, so the same allowance is thousands of lookups if you already know what to look up. You cannot buy more: the only route to a higher limit is an audit and extension request that takes weeks and is frequently refused. That means a tool built around searching for interesting videos does not work. A tool built around tracking channels you have chosen, fetching their uploads cheaply and computing outliers locally works comfortably inside the free allowance. Those are different products and the difference is forced by the quota rather than chosen. The second thing to get right is the statistics. A video with ten times the views of its channel's average is not necessarily interesting, because view counts are heavily skewed and an average is the wrong baseline. Compare against the channel's own distribution and against video age, since a two year old video has had two years to accumulate. ### Stack (fixed, do not substitute) - Collection: Playlist and video list operations, never search · search costs a hundred times what a list costs and the allowance is fixed - Caching: Aggressive local storage of everything fetched · the quota resets daily and refetching what you already have wastes it - Statistics: Comparison against a channel's own distribution, age adjusted · a channel mean is a bad baseline for a heavily skewed quantity - Scheduling: A daily budget spent deliberately across tracked channels · spending the allowance by lunchtime means no data for the rest of the day - Runtime: Python 3.12 with SQLite · the work is fetching, storing and computing ### Data model (create this before Phase 1) - channels(id, external_id, title, subscriber_count, tracked_since, uploads_playlist_id) - videos(id, channel_id, external_id, title, published_at, duration_seconds, first_seen_at) - snapshots(video_id, at, views, likes, comments) · a series, because velocity matters more than a total - baselines(channel_id, computed_at, median_views, quantiles_json, age_curve_json) · the channel's own distribution - outliers(video_id, computed_at, ratio_to_median, age_adjusted_ratio, percentile, confidence) - quota_ledger(day, operation, units, cumulative) · what the allowance was spent on - A snapshot series is kept rather than only the latest count, because a video reaching a million views in a week and one reaching it over three years are entirely different findings. ### Phase 1 · Spending the allowance deliberately Build: A quota ledger recording the unit cost of every request before it is made. Refuse a request that would exceed the daily allowance. Prefer list operations and make search either unavailable or explicitly rationed. Trap: A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. Done when: Every request records its unit cost before being issued and the ledger reconciles against the provider's own reported usage. A request that would exceed the daily allowance is refused rather than attempted. Search is either unavailable or explicitly rationed with its cost stated. Exhausting the allowance leaves the collected data consistent rather than partial. Do not build yet: No statistics, tracking or reporting. ### Phase 2 · Tracking channels cheaply Build: Fetch each channel's uploads through its playlist and retrieve statistics in batches. Cache everything and refetch only what could have changed. Handle a channel that disappears or makes videos private. Trap: Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. Done when: A hundred tracked channels update daily within the allowance, measured from the ledger. New uploads are fetched promptly while old videos are sampled on a decaying schedule. A deleted or private video is recorded as gone rather than dropped. Statistics are retrieved in batches rather than one call per video. Do not build yet: No baselines or outlier detection yet. ### Phase 3 · A baseline that is not the mean Build: Compute per-channel quantiles rather than a mean. Adjust for video age, since older videos have accumulated longer. Require a minimum number of videos before computing a baseline at all. Trap: Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. Done when: Baselines use quantiles rather than a mean, verified against a channel with one dominant video. Age adjustment is applied so a recent video is not penalised for having existed for a week. A channel with too few videos produces no baseline rather than an unreliable one. Recomputing a baseline is deterministic. Do not build yet: No outlier scoring or reporting yet. ### Phase 4 · Outliers with a confidence attached Build: Score videos against their channel's age-adjusted distribution, reporting a percentile and a confidence reflecting catalogue size and data recency. Use the snapshot series so velocity is distinguishable from accumulation. Trap: Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. Done when: Outlier scores carry a percentile and a confidence reflecting catalogue size and data age. A video that accumulated views slowly over years is distinguished from one that did so in a week, using the snapshot series. A channel with a small catalogue produces low-confidence results rather than none or overconfident ones. Scores are reproducible from stored snapshots. Do not build yet: No reporting or export yet. ### Phase 5 · Reporting without over-claiming Build: Report outliers with their statistics and links, filterable by channel, topic and recency. State plainly that the tool identifies unusual performance and does not explain it. Export the data with its snapshots so a finding can be rechecked. Trap: Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it. Done when: Reports show statistics and links with no causal explanation anywhere, verified by reading the output. Filters by channel, recency and confidence work. Export includes snapshots so a finding can be rechecked later. The quota ledger is visible so the day's remaining allowance is known. Do not build yet: Nothing after this. ### Out of scope (and why) - Search-driven discovery of channels you do not already know, which the quota makes impractical at any useful scale. - Explaining why a video performed well, which the data cannot support and which people will act on if you provide it. - Thumbnail and title analysis, which is where these tools make their claims and where the evidence is weakest. - Scraping the site instead of using the API, which is against the terms and is the obvious workaround for the quota. ### README must contain - That the daily allowance is fixed at ten thousand units, a search costs a hundred and a list costs one - That the quota cannot be bought and an extension requires an audit that is frequently refused - That baselines use quantiles rather than a mean, because one viral video ruins an average - That outlier scores carry a confidence derived from catalogue size and data recency - That the tool reports unusual performance and never explains it ===== ARCHITECTURE.md ===== # Architecture · 1of10 ## Stack | Part | Choice | Why | | --- | --- | --- | | Collection | Playlist and video list operations, never search | search costs a hundred times what a list costs and the allowance is fixed | | Caching | Aggressive local storage of everything fetched | the quota resets daily and refetching what you already have wastes it | | Statistics | Comparison against a channel's own distribution, age adjusted | a channel mean is a bad baseline for a heavily skewed quantity | | Scheduling | A daily budget spent deliberately across tracked channels | spending the allowance by lunchtime means no data for the rest of the day | | Runtime | Python 3.12 with SQLite | the work is fetching, storing and computing | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Quota | Budgeting and the spend ledger. | Not swappable for optimistic calling, which exhausts the allowance mid-run. | | Collection | Cheap list-based fetching and caching. | Any implementation, provided search stays rationed rather than routine. | | Baseline | Per-channel quantiles with age adjustment. | Any statistic, provided it is not a mean. | | Outliers | Scoring with confidence. | Any scoring, provided catalogue size affects the reported confidence. | | Reporting | Statistics without explanation. | Any presentation, provided no causal claim is attached. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Channels, videos and the snapshot series. - `YOUTUBE_API_KEY` · required, secret · Subject to the daily allowance, which shapes the whole build. - `DAILY_QUOTA_UNITS` · required · The allowance. Requests are budgeted against it before being issued. - `ALLOW_SEARCH` · required · A search costs a hundred units. Rationed or unavailable, never routine. - `MIN_CATALOGUE_FOR_BASELINE` · required · Below this no baseline is computed, because it would describe nothing. - `SNAPSHOT_RETENTION_DAYS` · required · The series is the data. Keep it far longer than the latest reading. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · 1of10 product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Playlist and video list operations, never search, Aggressive local storage of everything fetched, Comparison against a channel's own distribution, age adjusted, A daily budget spent deliberately across tracked channels, Python 3.12 with SQLite. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. - Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it. ===== MILESTONES.md ===== # Delivery milestones · 1of10 Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Spending the allowance deliberately Every request is budgeted and the day's allowance is never exhausted by accident. ### Steps 1. Budget before every call Refusing is cheaper than a quota error halfway through a run. Files: `src/quota.py`, `tests/test_quota.py` 2. Prefer list over search One unit against a hundred. This decides what the product can be. Files: `src/fetch.py` 3. Reconcile the ledger against reported usage So your accounting matches theirs rather than diverging quietly. Files: `src/quota.py` ### Done when - [ ] Every request records its cost before issue - [ ] Over-budget requests are refused - [ ] Search is unavailable or explicitly rationed - [ ] Exhaustion leaves consistent data ### Watch out - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. ## M2 · Tracking channels cheaply A hundred channels update daily inside the free allowance. ### Steps 1. Fetch uploads through the playlist One unit per page rather than a hundred per search. Files: `src/channels.py` 2. Batch statistics requests Fifty video identifiers per call rather than fifty calls. Files: `src/stats.py` 3. Sample old videos on a decaying schedule Their counts barely move and the allowance is better spent elsewhere. Files: `src/schedule.py` ### Done when - [ ] A hundred channels update daily within the allowance - [ ] New uploads are fetched promptly - [ ] Deleted videos are recorded as gone - [ ] Statistics are batched ### Watch out - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. ## M3 · A baseline that is not the mean Each channel is compared against its own distribution rather than an average. ### Steps 1. Use quantiles, not the mean One viral video drags a mean above the whole catalogue. Files: `src/baseline.py`, `tests/test_baseline.py` 2. Adjust for age A week-old video has not had the time an old one has. Files: `src/age.py` 3. Refuse below a minimum catalogue size A baseline from six videos describes nothing. Files: `src/baseline.py` ### Done when - [ ] Baselines use quantiles rather than a mean - [ ] Age adjustment is applied - [ ] Small catalogues produce no baseline - [ ] Recomputation is deterministic ### Watch out - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. ## M4 · Outliers with a confidence attached A flagged video is genuinely unusual for that channel rather than merely large. ### Steps 1. Score against the age-adjusted distribution Percentile, not a bare multiple of the median. Files: `src/outliers.py` 2. Attach a confidence Catalogue size and data recency. Fifteen videos is not four hundred. Files: `src/outliers.py` 3. Use the snapshot series for velocity A million views in a week is a different finding from over three years. Files: `src/velocity.py` ### Done when - [ ] Scores carry percentile and confidence - [ ] Velocity is distinguished from accumulation - [ ] Small catalogues give low confidence rather than false certainty - [ ] Scores reproduce from stored snapshots ### Watch out - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. ## M5 · Reporting without over-claiming (production only) The output says what was found and refuses to explain why it happened. ### Steps 1. Report statistics, not explanations The data supports the first and not the second. Files: `src/report.py` 2. Filter by confidence as well as size A large ratio with low confidence is not a finding. Files: `src/report.py` 3. Export with snapshots So a finding can be rechecked rather than believed. Files: `src/export.py` ### Done when - [ ] Reports contain no causal explanation - [ ] Filters include confidence - [ ] Export includes the snapshot series - [ ] Remaining quota is visible ### Watch out - Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it. ===== OPERATIONS.md ===== # Operations · 1of10 ## Backup The snapshot series is the irreplaceable asset, because it cannot be reconstructed later at any quota cost. Back it up; everything else regenerates from it. ## Restore Restore and confirm the snapshot series is continuous, since a gap in the middle makes velocity calculations across it wrong in a way that is not obvious from the results. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Daily quota consumption against the allowance is the operational number. A run consistently finishing at ninety percent will fail the first day a channel publishes more than usual. ## Incident checklist A quota error partway through collection leaves a partial day, and the fix is budgeting before the call rather than retrying. The ledger is what tells you which channels were missed. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Every request is confirmed budgeted before issue - [ ] A hundred channels are confirmed to update within the allowance - [ ] Baselines are confirmed to use quantiles rather than a mean - [ ] Scores are confirmed to carry a confidence - [ ] Reports are confirmed to contain no causal explanation ## Launch constraint Do not market omitted 1of10 capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Channels, videos and the snapshot series. DB_PATH=./research.db # Required · secret. Subject to the daily allowance, which shapes the whole build. YOUTUBE_API_KEY=a project API key # Required. The allowance. Requests are budgeted against it before being issued. DAILY_QUOTA_UNITS=10000 # Required. A search costs a hundred units. Rationed or unavailable, never routine. ALLOW_SEARCH=false # Required. Below this no baseline is computed, because it would describe nothing. MIN_CATALOGUE_FOR_BASELINE=30 # Required. The series is the data. Keep it far longer than the latest reading. SNAPSHOT_RETENTION_DAYS=730
# 1of10 · indie build A research tool that finds videos performing far above their own channel's normal range, where the engineering is entirely shaped by an API quota that makes search unaffordable and cheap list operations the only viable path. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Collection | Playlist and video list operations, never search | search costs a hundred times what a list costs and the allowance is fixed | | Caching | Aggressive local storage of everything fetched | the quota resets daily and refetching what you already have wastes it | | Statistics | Comparison against a channel's own distribution, age adjusted | a channel mean is a bad baseline for a heavily skewed quantity | | Scheduling | A daily budget spent deliberately across tracked channels | spending the allowance by lunchtime means no data for the rest of the day | | Runtime | Python 3.12 with SQLite | the work is fetching, storing and computing | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs collection and the statistics. - Get it: python.org or your package manager, then a virtual environment. - Verify: python3 --version prints 3.12 or higher - [ ] **A YouTube Data API key with its quota understood** · free within the allowance; extensions require an audit - Why: The whole architecture follows from the allowance, and building before reading the quota costs a rewrite rather than a tuning pass. - Get it: Create a project, enable the API, and read the quota cost table before writing the fetch layer. - Verify: A list request and a search request are both issued and their unit costs confirmed against the documentation - [ ] **Fifty channels you actually want to track** · free - Why: The build is designed around tracking a chosen set rather than searching, so the set has to exist before the collection schedule makes sense. - Get it: List channels in your area of interest, including at least one with very few uploads and one with several hundred. - Verify: Fifty channel identifiers are recorded including a small and a large catalogue - [ ] **Two weeks of collected snapshots** · free, and it takes two weeks - Why: Velocity, which is the difference between a genuinely interesting video and an old one, requires a series rather than a single reading. - Get it: Run collection daily for two weeks before evaluating the outlier logic. - Verify: At least fourteen snapshots exist per tracked video - [ ] **Your outlier definition, written down** · free - Why: A multiple of the median, a percentile and an age-adjusted percentile give different answers, and picking one afterwards means picking whichever produces the results you liked. - Get it: Decide the definition and the minimum catalogue size before running it against real channels. - Verify: A written definition exists including the minimum catalogue size ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. - Cross-channel discovery: you can only find outliers in channels you already listed - Historical depth, their index has view curves from before you started collecting - Quota headroom, refreshing thousands of channels daily needs paid access or many keys - Curated niche collections, thumbnail galleries and saved-idea workflows - Shorts vs long-form normalization and view-velocity nuance that took them iterations to get right If one of those is essential to you, that is the reason to keep paying for 1of10, and the README should say so rather than pretend.
# Build brief · 1of10 The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me finding videos that massively outperformed their channel's own baseline to replace 1of10. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The quota decides the architecture here, so read it before designing anything. The YouTube Data API gives 10,000 units a day per project. A search request costs 100 units, so the free allowance is about a hundred searches a day. A list request costs one unit, so the same allowance is thousands of lookups if you already know what to look up. You cannot buy more: the only route to a higher limit is an audit and extension request that takes weeks and is frequently refused. That means a tool built around searching for interesting videos does not work. A tool built around tracking channels you have chosen, fetching their uploads cheaply and computing outliers locally works comfortably inside the free allowance. Those are different products and the difference is forced by the quota rather than chosen. The second thing to get right is the statistics. A video with ten times the views of its channel's average is not necessarily interesting, because view counts are heavily skewed and an average is the wrong baseline. Compare against the channel's own distribution and against video age, since a two year old video has had two years to accumulate. ### Stack (fixed, do not substitute) - Collection: Playlist and video list operations, never search · search costs a hundred times what a list costs and the allowance is fixed - Caching: Aggressive local storage of everything fetched · the quota resets daily and refetching what you already have wastes it - Statistics: Comparison against a channel's own distribution, age adjusted · a channel mean is a bad baseline for a heavily skewed quantity - Scheduling: A daily budget spent deliberately across tracked channels · spending the allowance by lunchtime means no data for the rest of the day - Runtime: Python 3.12 with SQLite · the work is fetching, storing and computing ### Data model (create this before Phase 1) - channels(id, external_id, title, subscriber_count, tracked_since, uploads_playlist_id) - videos(id, channel_id, external_id, title, published_at, duration_seconds, first_seen_at) - snapshots(video_id, at, views, likes, comments) · a series, because velocity matters more than a total - baselines(channel_id, computed_at, median_views, quantiles_json, age_curve_json) · the channel's own distribution - outliers(video_id, computed_at, ratio_to_median, age_adjusted_ratio, percentile, confidence) - quota_ledger(day, operation, units, cumulative) · what the allowance was spent on - A snapshot series is kept rather than only the latest count, because a video reaching a million views in a week and one reaching it over three years are entirely different findings. ### Phase 1 · Spending the allowance deliberately Build: A quota ledger recording the unit cost of every request before it is made. Refuse a request that would exceed the daily allowance. Prefer list operations and make search either unavailable or explicitly rationed. Trap: A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. Done when: Every request records its unit cost before being issued and the ledger reconciles against the provider's own reported usage. A request that would exceed the daily allowance is refused rather than attempted. Search is either unavailable or explicitly rationed with its cost stated. Exhausting the allowance leaves the collected data consistent rather than partial. Do not build yet: No statistics, tracking or reporting. ### Phase 2 · Tracking channels cheaply Build: Fetch each channel's uploads through its playlist and retrieve statistics in batches. Cache everything and refetch only what could have changed. Handle a channel that disappears or makes videos private. Trap: Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. Done when: A hundred tracked channels update daily within the allowance, measured from the ledger. New uploads are fetched promptly while old videos are sampled on a decaying schedule. A deleted or private video is recorded as gone rather than dropped. Statistics are retrieved in batches rather than one call per video. Do not build yet: No baselines or outlier detection yet. ### Phase 3 · A baseline that is not the mean Build: Compute per-channel quantiles rather than a mean. Adjust for video age, since older videos have accumulated longer. Require a minimum number of videos before computing a baseline at all. Trap: Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. Done when: Baselines use quantiles rather than a mean, verified against a channel with one dominant video. Age adjustment is applied so a recent video is not penalised for having existed for a week. A channel with too few videos produces no baseline rather than an unreliable one. Recomputing a baseline is deterministic. Do not build yet: No outlier scoring or reporting yet. ### Phase 4 · Outliers with a confidence attached Build: Score videos against their channel's age-adjusted distribution, reporting a percentile and a confidence reflecting catalogue size and data recency. Use the snapshot series so velocity is distinguishable from accumulation. Trap: Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. Done when: Outlier scores carry a percentile and a confidence reflecting catalogue size and data age. A video that accumulated views slowly over years is distinguished from one that did so in a week, using the snapshot series. A channel with a small catalogue produces low-confidence results rather than none or overconfident ones. Scores are reproducible from stored snapshots. Do not build yet: No reporting or export yet. ### Phase 5 · Reporting without over-claiming Build: Report outliers with their statistics and links, filterable by channel, topic and recency. State plainly that the tool identifies unusual performance and does not explain it. Export the data with its snapshots so a finding can be rechecked. Trap: Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it. Done when: Reports show statistics and links with no causal explanation anywhere, verified by reading the output. Filters by channel, recency and confidence work. Export includes snapshots so a finding can be rechecked later. The quota ledger is visible so the day's remaining allowance is known. Do not build yet: Nothing after this. ### Out of scope (and why) - Search-driven discovery of channels you do not already know, which the quota makes impractical at any useful scale. - Explaining why a video performed well, which the data cannot support and which people will act on if you provide it. - Thumbnail and title analysis, which is where these tools make their claims and where the evidence is weakest. - Scraping the site instead of using the API, which is against the terms and is the obvious workaround for the quota. ### README must contain - That the daily allowance is fixed at ten thousand units, a search costs a hundred and a list costs one - That the quota cannot be bought and an extension requires an audit that is frequently refused - That baselines use quantiles rather than a mean, because one viral video ruins an average - That outlier scores carry a confidence derived from catalogue size and data recency - That the tool reports unusual performance and never explains it
# Agent instructions · 1of10 indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Playlist and video list operations, never search, Aggressive local storage of everything fetched, Comparison against a channel's own distribution, age adjusted, A daily budget spent deliberately across tracked channels, Python 3.12 with SQLite. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected.
# Build plan · 1of10 A research tool that finds videos performing far above their own channel's normal range, where the engineering is entirely shaped by an API quota that makes search unaffordable and cheap list operations the only viable path. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Spending the allowance deliberately Every request is budgeted and the day's allowance is never exhausted by accident. ### Steps 1. Budget before every call Refusing is cheaper than a quota error halfway through a run. Files: `src/quota.py`, `tests/test_quota.py` 2. Prefer list over search One unit against a hundred. This decides what the product can be. Files: `src/fetch.py` 3. Reconcile the ledger against reported usage So your accounting matches theirs rather than diverging quietly. Files: `src/quota.py` ### Done when - [ ] Every request records its cost before issue - [ ] Over-budget requests are refused - [ ] Search is unavailable or explicitly rationed - [ ] Exhaustion leaves consistent data ### Watch out - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. ## Phase 2 · Tracking channels cheaply A hundred channels update daily inside the free allowance. ### Steps 1. Fetch uploads through the playlist One unit per page rather than a hundred per search. Files: `src/channels.py` 2. Batch statistics requests Fifty video identifiers per call rather than fifty calls. Files: `src/stats.py` 3. Sample old videos on a decaying schedule Their counts barely move and the allowance is better spent elsewhere. Files: `src/schedule.py` ### Done when - [ ] A hundred channels update daily within the allowance - [ ] New uploads are fetched promptly - [ ] Deleted videos are recorded as gone - [ ] Statistics are batched ### Watch out - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. ## Phase 3 · A baseline that is not the mean Each channel is compared against its own distribution rather than an average. ### Steps 1. Use quantiles, not the mean One viral video drags a mean above the whole catalogue. Files: `src/baseline.py`, `tests/test_baseline.py` 2. Adjust for age A week-old video has not had the time an old one has. Files: `src/age.py` 3. Refuse below a minimum catalogue size A baseline from six videos describes nothing. Files: `src/baseline.py` ### Done when - [ ] Baselines use quantiles rather than a mean - [ ] Age adjustment is applied - [ ] Small catalogues produce no baseline - [ ] Recomputation is deterministic ### Watch out - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. ## Phase 4 · Outliers with a confidence attached A flagged video is genuinely unusual for that channel rather than merely large. ### Steps 1. Score against the age-adjusted distribution Percentile, not a bare multiple of the median. Files: `src/outliers.py` 2. Attach a confidence Catalogue size and data recency. Fifteen videos is not four hundred. Files: `src/outliers.py` 3. Use the snapshot series for velocity A million views in a week is a different finding from over three years. Files: `src/velocity.py` ### Done when - [ ] Scores carry percentile and confidence - [ ] Velocity is distinguished from accumulation - [ ] Small catalogues give low confidence rather than false certainty - [ ] Scores reproduce from stored snapshots ### Watch out - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. ## Not in this build - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. ## After v1, if you want it - Alerting when a tracked channel publishes an outlier, which is the useful daily output - Cross-channel comparison within a topic, once baselines are trustworthy - Retention of removed videos as a record, since disappearance is itself information
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Channels, videos and the snapshot series. DB_PATH=./research.db # Required · secret. Subject to the daily allowance, which shapes the whole build. YOUTUBE_API_KEY=a project API key # Required. The allowance. Requests are budgeted against it before being issued. DAILY_QUOTA_UNITS=10000 # Required. A search costs a hundred units. Rationed or unavailable, never routine. ALLOW_SEARCH=false # Required. Below this no baseline is computed, because it would describe nothing. MIN_CATALOGUE_FOR_BASELINE=30 # Required. The series is the data. Keep it far longer than the latest reading. SNAPSHOT_RETENTION_DAYS=730
# 1of10 · product brief ## Problem The core math is embarrassingly simple: pull a channel's uploads, compute a median or trailing baseline, divide each video's views by it, sort descending. The YouTube Data API hands you everything you need for that, and an agent can wire up a local outlier dashboard for a list of channels you care about in one sitting. Where it breaks down is scale: 1of10's real product is a pre-indexed corpus of millions of videos you can search across niches you have never heard of, with history that predates your interest. Your build can only see channels you thought to track, and the free API quota caps how many you can refresh per day. Good enough for watching 50 competitors, useless for open-ended idea mining. ## Product outcome A research tool that lives comfortably inside a fixed free allowance, compares each video against its own channel's distribution, and never pretends to know why something worked. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - YouTube Data API v3 key (free tier, 10k units/day) - Python 3.11 and a machine or cheap VPS to run a daily refresh - A hand-curated list of channels to track ## Explicit non-goals for v1 - Search-driven discovery of unknown channels. - Explanations for why a video performed well. - Thumbnail and title analysis. - Scraping instead of using the API. - Cross-channel discovery: you can only find outliers in channels you already listed - Historical depth, their index has view curves from before you started collecting - Quota headroom, refreshing thousands of channels daily needs paid access or many keys - Curated niche collections, thumbnail galleries and saved-idea workflows - Shorts vs long-form normalization and view-velocity nuance that took them iterations to get right ## Success criteria - Every request is confirmed budgeted before issue - A hundred channels are confirmed to update within the allowance - Baselines are confirmed to use quantiles rather than a mean - Scores are confirmed to carry a confidence - Reports are confirmed to contain no causal explanation
# Build brief · 1of10 The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me finding videos that massively outperformed their channel's own baseline to replace 1of10. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The quota decides the architecture here, so read it before designing anything. The YouTube Data API gives 10,000 units a day per project. A search request costs 100 units, so the free allowance is about a hundred searches a day. A list request costs one unit, so the same allowance is thousands of lookups if you already know what to look up. You cannot buy more: the only route to a higher limit is an audit and extension request that takes weeks and is frequently refused. That means a tool built around searching for interesting videos does not work. A tool built around tracking channels you have chosen, fetching their uploads cheaply and computing outliers locally works comfortably inside the free allowance. Those are different products and the difference is forced by the quota rather than chosen. The second thing to get right is the statistics. A video with ten times the views of its channel's average is not necessarily interesting, because view counts are heavily skewed and an average is the wrong baseline. Compare against the channel's own distribution and against video age, since a two year old video has had two years to accumulate. ### Stack (fixed, do not substitute) - Collection: Playlist and video list operations, never search · search costs a hundred times what a list costs and the allowance is fixed - Caching: Aggressive local storage of everything fetched · the quota resets daily and refetching what you already have wastes it - Statistics: Comparison against a channel's own distribution, age adjusted · a channel mean is a bad baseline for a heavily skewed quantity - Scheduling: A daily budget spent deliberately across tracked channels · spending the allowance by lunchtime means no data for the rest of the day - Runtime: Python 3.12 with SQLite · the work is fetching, storing and computing ### Data model (create this before Phase 1) - channels(id, external_id, title, subscriber_count, tracked_since, uploads_playlist_id) - videos(id, channel_id, external_id, title, published_at, duration_seconds, first_seen_at) - snapshots(video_id, at, views, likes, comments) · a series, because velocity matters more than a total - baselines(channel_id, computed_at, median_views, quantiles_json, age_curve_json) · the channel's own distribution - outliers(video_id, computed_at, ratio_to_median, age_adjusted_ratio, percentile, confidence) - quota_ledger(day, operation, units, cumulative) · what the allowance was spent on - A snapshot series is kept rather than only the latest count, because a video reaching a million views in a week and one reaching it over three years are entirely different findings. ### Phase 1 · Spending the allowance deliberately Build: A quota ledger recording the unit cost of every request before it is made. Refuse a request that would exceed the daily allowance. Prefer list operations and make search either unavailable or explicitly rationed. Trap: A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. Done when: Every request records its unit cost before being issued and the ledger reconciles against the provider's own reported usage. A request that would exceed the daily allowance is refused rather than attempted. Search is either unavailable or explicitly rationed with its cost stated. Exhausting the allowance leaves the collected data consistent rather than partial. Do not build yet: No statistics, tracking or reporting. ### Phase 2 · Tracking channels cheaply Build: Fetch each channel's uploads through its playlist and retrieve statistics in batches. Cache everything and refetch only what could have changed. Handle a channel that disappears or makes videos private. Trap: Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. Done when: A hundred tracked channels update daily within the allowance, measured from the ledger. New uploads are fetched promptly while old videos are sampled on a decaying schedule. A deleted or private video is recorded as gone rather than dropped. Statistics are retrieved in batches rather than one call per video. Do not build yet: No baselines or outlier detection yet. ### Phase 3 · A baseline that is not the mean Build: Compute per-channel quantiles rather than a mean. Adjust for video age, since older videos have accumulated longer. Require a minimum number of videos before computing a baseline at all. Trap: Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. Done when: Baselines use quantiles rather than a mean, verified against a channel with one dominant video. Age adjustment is applied so a recent video is not penalised for having existed for a week. A channel with too few videos produces no baseline rather than an unreliable one. Recomputing a baseline is deterministic. Do not build yet: No outlier scoring or reporting yet. ### Phase 4 · Outliers with a confidence attached Build: Score videos against their channel's age-adjusted distribution, reporting a percentile and a confidence reflecting catalogue size and data recency. Use the snapshot series so velocity is distinguishable from accumulation. Trap: Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. Done when: Outlier scores carry a percentile and a confidence reflecting catalogue size and data age. A video that accumulated views slowly over years is distinguished from one that did so in a week, using the snapshot series. A channel with a small catalogue produces low-confidence results rather than none or overconfident ones. Scores are reproducible from stored snapshots. Do not build yet: No reporting or export yet. ### Phase 5 · Reporting without over-claiming Build: Report outliers with their statistics and links, filterable by channel, topic and recency. State plainly that the tool identifies unusual performance and does not explain it. Export the data with its snapshots so a finding can be rechecked. Trap: Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it. Done when: Reports show statistics and links with no causal explanation anywhere, verified by reading the output. Filters by channel, recency and confidence work. Export includes snapshots so a finding can be rechecked later. The quota ledger is visible so the day's remaining allowance is known. Do not build yet: Nothing after this. ### Out of scope (and why) - Search-driven discovery of channels you do not already know, which the quota makes impractical at any useful scale. - Explaining why a video performed well, which the data cannot support and which people will act on if you provide it. - Thumbnail and title analysis, which is where these tools make their claims and where the evidence is weakest. - Scraping the site instead of using the API, which is against the terms and is the obvious workaround for the quota. ### README must contain - That the daily allowance is fixed at ten thousand units, a search costs a hundred and a list costs one - That the quota cannot be bought and an extension requires an audit that is frequently refused - That baselines use quantiles rather than a mean, because one viral video ruins an average - That outlier scores carry a confidence derived from catalogue size and data recency - That the tool reports unusual performance and never explains it
# Architecture · 1of10 ## Stack | Part | Choice | Why | | --- | --- | --- | | Collection | Playlist and video list operations, never search | search costs a hundred times what a list costs and the allowance is fixed | | Caching | Aggressive local storage of everything fetched | the quota resets daily and refetching what you already have wastes it | | Statistics | Comparison against a channel's own distribution, age adjusted | a channel mean is a bad baseline for a heavily skewed quantity | | Scheduling | A daily budget spent deliberately across tracked channels | spending the allowance by lunchtime means no data for the rest of the day | | Runtime | Python 3.12 with SQLite | the work is fetching, storing and computing | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Quota | Budgeting and the spend ledger. | Not swappable for optimistic calling, which exhausts the allowance mid-run. | | Collection | Cheap list-based fetching and caching. | Any implementation, provided search stays rationed rather than routine. | | Baseline | Per-channel quantiles with age adjustment. | Any statistic, provided it is not a mean. | | Outliers | Scoring with confidence. | Any scoring, provided catalogue size affects the reported confidence. | | Reporting | Statistics without explanation. | Any presentation, provided no causal claim is attached. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Channels, videos and the snapshot series. - `YOUTUBE_API_KEY` · required, secret · Subject to the daily allowance, which shapes the whole build. - `DAILY_QUOTA_UNITS` · required · The allowance. Requests are budgeted against it before being issued. - `ALLOW_SEARCH` · required · A search costs a hundred units. Rationed or unavailable, never routine. - `MIN_CATALOGUE_FOR_BASELINE` · required · Below this no baseline is computed, because it would describe nothing. - `SNAPSHOT_RETENTION_DAYS` · required · The series is the data. Keep it far longer than the latest reading. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · 1of10 product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Playlist and video list operations, never search, Aggressive local storage of everything fetched, Comparison against a channel's own distribution, age adjusted, A daily budget spent deliberately across tracked channels, Python 3.12 with SQLite. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. - Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it.
# Delivery milestones · 1of10 Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Spending the allowance deliberately Every request is budgeted and the day's allowance is never exhausted by accident. ### Steps 1. Budget before every call Refusing is cheaper than a quota error halfway through a run. Files: `src/quota.py`, `tests/test_quota.py` 2. Prefer list over search One unit against a hundred. This decides what the product can be. Files: `src/fetch.py` 3. Reconcile the ledger against reported usage So your accounting matches theirs rather than diverging quietly. Files: `src/quota.py` ### Done when - [ ] Every request records its cost before issue - [ ] Over-budget requests are refused - [ ] Search is unavailable or explicitly rationed - [ ] Exhaustion leaves consistent data ### Watch out - A single loop calling search for each of a hundred channels exhausts the entire daily allowance in one run, and the failure arrives as a quota error partway through with half the data collected. Budget before the call, record the cost, and make search a rationed operation rather than a normal one. ## M2 · Tracking channels cheaply A hundred channels update daily inside the free allowance. ### Steps 1. Fetch uploads through the playlist One unit per page rather than a hundred per search. Files: `src/channels.py` 2. Batch statistics requests Fifty video identifiers per call rather than fifty calls. Files: `src/stats.py` 3. Sample old videos on a decaying schedule Their counts barely move and the allowance is better spent elsewhere. Files: `src/schedule.py` ### Done when - [ ] A hundred channels update daily within the allowance - [ ] New uploads are fetched promptly - [ ] Deleted videos are recorded as gone - [ ] Statistics are batched ### Watch out - Refetching a channel's entire history every day wastes the allowance on videos whose counts barely move, and leaves nothing for the new uploads that are the point. Fetch new uploads fully, sample older videos on a decaying schedule, and record when each was last seen. ## M3 · A baseline that is not the mean Each channel is compared against its own distribution rather than an average. ### Steps 1. Use quantiles, not the mean One viral video drags a mean above the whole catalogue. Files: `src/baseline.py`, `tests/test_baseline.py` 2. Adjust for age A week-old video has not had the time an old one has. Files: `src/age.py` 3. Refuse below a minimum catalogue size A baseline from six videos describes nothing. Files: `src/baseline.py` ### Done when - [ ] Baselines use quantiles rather than a mean - [ ] Age adjustment is applied - [ ] Small catalogues produce no baseline - [ ] Recomputation is deterministic ### Watch out - Comparing against a channel's mean views makes almost everything look below average, because one viral video drags the mean above nearly the whole catalogue. Use the median and quantiles, which describe where a video actually sits among its siblings. ## M4 · Outliers with a confidence attached A flagged video is genuinely unusual for that channel rather than merely large. ### Steps 1. Score against the age-adjusted distribution Percentile, not a bare multiple of the median. Files: `src/outliers.py` 2. Attach a confidence Catalogue size and data recency. Fifteen videos is not four hundred. Files: `src/outliers.py` 3. Use the snapshot series for velocity A million views in a week is a different finding from over three years. Files: `src/velocity.py` ### Done when - [ ] Scores carry percentile and confidence - [ ] Velocity is distinguished from accumulation - [ ] Small catalogues give low confidence rather than false certainty - [ ] Scores reproduce from stored snapshots ### Watch out - Reporting a ratio against the median without saying how many videos the median came from treats a channel with four hundred uploads and one with fifteen as equally reliable, which they are not. Attach a confidence derived from catalogue size and how recently the data was collected. ## M5 · Reporting without over-claiming (production only) The output says what was found and refuses to explain why it happened. ### Steps 1. Report statistics, not explanations The data supports the first and not the second. Files: `src/report.py` 2. Filter by confidence as well as size A large ratio with low confidence is not a finding. Files: `src/report.py` 3. Export with snapshots So a finding can be rechecked rather than believed. Files: `src/export.py` ### Done when - [ ] Reports contain no causal explanation - [ ] Filters include confidence - [ ] Export includes the snapshot series - [ ] Remaining quota is visible ### Watch out - Attaching an explanation for why a video outperformed turns a measurement into a claim about causation that the data cannot support, and people will act on it. Report the statistics, link to the video, and leave the interpretation to the person who can actually watch it.
# Operations · 1of10 ## Backup The snapshot series is the irreplaceable asset, because it cannot be reconstructed later at any quota cost. Back it up; everything else regenerates from it. ## Restore Restore and confirm the snapshot series is continuous, since a gap in the middle makes velocity calculations across it wrong in a way that is not obvious from the results. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Daily quota consumption against the allowance is the operational number. A run consistently finishing at ninety percent will fail the first day a channel publishes more than usual. ## Incident checklist A quota error partway through collection leaves a partial day, and the fix is budgeting before the call rather than retrying. The ledger is what tells you which channels were missed. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Every request is confirmed budgeted before issue - [ ] A hundred channels are confirmed to update within the allowance - [ ] Baselines are confirmed to use quantiles rather than a mean - [ ] Scores are confirmed to carry a confidence - [ ] Reports are confirmed to contain no causal explanation ## Launch constraint Do not market omitted 1of10 capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Channels, videos and the snapshot series. DB_PATH=./research.db # Required · secret. Subject to the daily allowance, which shapes the whole build. YOUTUBE_API_KEY=a project API key # Required. The allowance. Requests are budgeted against it before being issued. DAILY_QUOTA_UNITS=10000 # Required. A search costs a hundred units. Rationed or unavailable, never routine. ALLOW_SEARCH=false # Required. Below this no baseline is computed, because it would describe nothing. MIN_CATALOGUE_FOR_BASELINE=30 # Required. The series is the data. Keep it far longer than the latest reading. SNAPSHOT_RETENTION_DAYS=730
$ choose a build depth, inspect the files, then open the complete pack in your agent
Because the point of outlier research is finding formats in corners of YouTube you would never think to monitor, and that requires a crawler that has been running for years on someone else's quota budget. A personal tracker answers "what is working for my competitors" nicely. It cannot answer "what format is quietly exploding in a niche adjacent to mine", which is the question people actually pay for.
xCross-channel discovery: you can only find outliers in channels you already listed
xHistorical depth, their index has view curves from before you started collecting
xQuota headroom, refreshing thousands of channels daily needs paid access or many keys
xCurated niche collections, thumbnail galleries and saved-idea workflows
xShorts vs long-form normalization and view-velocity nuance that took them iterations to get right
Nothing worth pointing at. That's why the prompt exists.
Vibecode 1of10
Kinda. The core of 1of10 is buildable in a weekend with the prompt on this page, but there are real gaps: Cross-channel discovery: you can only find outliers in channels you already listed, Historical depth, their index has view curves from before you started collecting. Read the honest list above before committing.
How much does 1of10 cost?
1of10 costs about $29/month (Basic, checked 2026-08-18), which is $348 per year.
What do I lose by replacing 1of10?
Honestly: Cross-channel discovery: you can only find outliers in channels you already listed; Historical depth, their index has view curves from before you started collecting; Quota headroom, refreshing thousands of channels daily needs paid access or many keys; Curated niche collections, thumbnail galleries and saved-idea workflows; Shorts vs long-form normalization and view-velocity nuance that took them iterations to get right. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to 1of10?
No mature open-source alternative worth pointing at, which is exactly why the one-shot prompt on this page exists.