Docs/Skills and code/src/

src/series-analysis/queries.js

Code93 lines4.2 KBsrc/

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.