Docs/Skills and code/src/
src/series-analysis/queries.js
import { norm } from "./signals.js";
const TIER_SHARES = {
exact: 0.34,
"exact-hinglish": 0.12,
"adjacent-packaging": 0.22,
generic: 0.1,
"adjacent-topic": 0.1,
"reference-channel": 0.12
};
/**
* Builds the search plan. Tiers say how close a query sits to the series packaging:
* exact "Why Kingfisher Airlines Died": the series formula on an Indian company
* exact-hinglish same, romanised Hindi (how much of this audience searches)
* adjacent-packaging same disaster framing with another formula or a non-Indian company
* generic category level ("biggest business failures in India")
* adjacent-topic neighbouring story types (scams, founder downfalls)
* reference-channel what the known channels in this space publish
* Each tier gets a share of the budget and tiers are interleaved, so a small budget still covers all of them.
*/
export function buildQueries(spec, maxQueries = spec.budget?.maxQueries ?? 70) {
const indian = spec.companies?.indian ?? [], global = spec.companies?.global ?? [];
const pools = {
exact: cross(spec.titleFormulas, indian),
"exact-hinglish": cross(spec.hinglishFormulas ?? [], indian),
"adjacent-packaging": [
...cross(spec.titleFormulas.slice(0, 3), global),
...cross(spec.adjacentPackagingFormulas ?? [], [...indian, ...global])
],
generic: (spec.genericQueries ?? []).map(query => ({ query })),
"adjacent-topic": (spec.adjacentTopicQueries ?? []).map(query => ({ query })),
"reference-channel": (spec.referenceChannels ?? []).flatMap(anchor => [
{ query: `${anchor} company collapse case study`, anchor },
{ query: `${anchor} why it failed`, anchor }
])
};
const out = [], seen = new Set(), used = {};
const take = (tier, item) => {
const key = norm(item.query);
if (seen.has(key)) return false;
seen.add(key);
out.push({ tier, ...item });
used[tier] = (used[tier] ?? 0) + 1;
return true;
};
const quota = Object.fromEntries(Object.entries(TIER_SHARES).map(([tier, share]) => [tier, Math.max(1, Math.round(share * maxQueries))]));
const cursor = Object.fromEntries(Object.keys(pools).map(tier => [tier, 0]));
const next = tier => {
while (cursor[tier] < pools[tier].length) if (take(tier, pools[tier][cursor[tier]++])) return true;
return false;
};
let progressed = true;
while (out.length < maxQueries && progressed) {
progressed = false;
for (const tier of Object.keys(pools)) {
if (out.length >= maxQueries) break;
if ((used[tier] ?? 0) < quota[tier] && next(tier)) progressed = true;
}
}
// A pool that ran dry leaves budget behind; spend it on whichever pools still have queries.
for (const tier of Object.keys(pools)) while (out.length < maxQueries && next(tier));
return out;
}
/** Every company gets one formula before any company gets a second, rotating formulas so the first N queries are varied. */
function cross(formulas, companies) {
const items = [];
for (let pass = 0; pass < formulas.length; pass++) {
companies.forEach((company, ci) => {
const formula = formulas[(ci + pass) % formulas.length];
items.push({ query: formula.replace("{company}", company), formula, anchor: company });
});
}
return items;
}
/** Optional LLM expansion. The deterministic plan stays the backbone; this only adds companies and angles we didn't list. */
export function llmQueryPrompt(spec, existing, count = 15) {
const known = [...(spec.companies?.indian ?? []), ...(spec.companies?.global ?? [])].join(", ");
return {
system: "You design YouTube search queries. Reply with JSON only.",
user: `A creator wants to make this YouTube series: "${spec.name}". ${spec.premise}
Ecosystem: ${spec.ecosystem}. The audience searches in English and Hinglish.
Propose ${count} NEW search queries that would surface videos with the same or very similar packaging (title framing, thumbnail promise, story type). Use companies or brands NOT in this list: ${known}.
Do not repeat these existing queries: ${existing.slice(0, 40).join(" | ")}.
Return a JSON array of objects: {"query": string, "tier": "exact"|"adjacent-packaging"|"adjacent-topic", "why": string}.`
};
}
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.