Docs/Skills and code/src/

src/stages/31-within-channel-performance.js

Code109 lines6.0 KBsrc/

import { filterQualifiedRows } from "../analysis/qualification-scope.js";
import crypto from "node:crypto";
import path from "node:path";
import Database from "better-sqlite3";
import { config as projectConfig } from "../../config.js";
import { analyzeWithinChannelCorpus } from "../analysis/within-channel-performance.js";
import { loadWithinChannelInput, persistWithinChannelAnalysis } from "../analysis/within-channel-performance-store.js";
import { writeWithinChannelPerformanceReports } from "../analysis/within-channel-performance-report.js";

const args = process.argv.slice(2);
const has = flag => args.includes(flag);
const argValue = name => args.find(value => value.startsWith(`${name}=`))?.slice(name.length + 1);
const dryRun = has("--dry-run");
const generatedAt = new Date().toISOString();
const parsedNow = argValue("--as-of") ? Date.parse(argValue("--as-of")) : Date.parse(generatedAt);
if (!Number.isFinite(parsedNow)) throw new Error("--as-of must be a valid ISO date/time.");
const runId = `${generatedAt.replaceAll(":", "-").replaceAll(".", "-")}-${crypto.randomUUID().slice(0, 8)}`;
const root = process.cwd();
const databasePath = path.resolve(projectConfig.dbPath);
const db = new Database(databasePath, { fileMustExist: true });
db.pragma("foreign_keys = ON");

const analysisConfig = {
  absoluteReachFloor: 1000,
  subscriberReachFraction: 0.25,
  minAgeCohortSize: 20,
  sampleThresholds: { insufficientMax: 4, preliminaryMax: 9, initialOutlierMax: 19, distributionMax: 49, subgroupMin: 50 },
  lifetimeOutlierMethod: "log1p_views_median_mad; robust_z>=2.5 potential, >=3.5 strong; ratio fallback only when MAD=0",
  ageAdjustedMethod: "exact-calendar-publication-date views/day; channel-relative log1p median/MAD plus separate global age-cohort baseline",
  viewSelection: "latest observation among current videos row and newest non-null video_metric_snapshots row; precision retained",
  deduplication: "UNION DISTINCT (channel_url, video_id) from channel_videos and videos.channel_url",
  scoringBoundary: "observational_only; existing queue score and rank tables are not read or modified"
};

try {
  console.log("[within-channel:analyze] Starting", {
    runId,
    database: databasePath,
    mode: dryRun ? "DRY RUN (no tables or reports written)" : "PERSIST NEW ANALYSIS TABLES",
    asOf: new Date(parsedNow).toISOString(),
    queueScoresWillChange: false
  });
  const input = loadWithinChannelInput(db);
  input.channels = filterQualifiedRows(db, input.channels, "channel", { label: "31-within-channel-performance.js" });
  const eligibleUrls = new Set(input.channels.map(channel => channel.channel_url));
  input.videosByChannel = new Map([...input.videosByChannel.entries()].filter(([channelUrl]) => eligibleUrls.has(channelUrl)));
  input.uniquePairCount = [...input.videosByChannel.values()].reduce((sum, videos) => sum + videos.length, 0);
  const channelCounts = input.channels.length;
  const linkedCount = input.uniquePairCount;
  console.log("[within-channel:analyze] Source population", {
    channels: channelCounts,
    rawChannelVideoRelationshipRows: input.rawRelationCount,
    deduplicatedChannelVideoPairs: input.rawUniquePairCount,
    duplicateRelationshipRowsRemoved: input.rawRelationCount - input.rawUniquePairCount,
    supplementalPairsFromVideosTable: input.supplementalVideoPairs,
    finalUniqueChannelVideoPairs: linkedCount,
    videosWithRelationships: [...input.videosByChannel.values()].reduce((sum, rows) => sum + rows.length, 0)
  });

  const result = analyzeWithinChannelCorpus({
    channels: input.channels,
    videosByChannel: input.videosByChannel,
    nowMs: parsedNow,
    config: analysisConfig
  });
  const summary = {
    channels_analyzed: result.profiles.length,
    channels_with_zero_eligible_views: result.profiles.filter(row => row.eligible_view_count === 0).length,
    channels_with_under_5_eligible_views: result.profiles.filter(row => row.eligible_view_count < 5).length,
    channels_with_5_to_9_eligible_views: result.profiles.filter(row => row.eligible_view_count >= 5 && row.eligible_view_count <= 9).length,
    channels_with_10_to_19_eligible_views: result.profiles.filter(row => row.eligible_view_count >= 10 && row.eligible_view_count <= 19).length,
    channels_with_20_to_49_eligible_views: result.profiles.filter(row => row.eligible_view_count >= 20 && row.eligible_view_count <= 49).length,
    channels_with_50_plus_eligible_views: result.profiles.filter(row => row.eligible_view_count >= 50).length,
    unique_channel_video_pairs: linkedCount,
    video_outlier_rows: result.outliers.length,
    eligible_video_records: result.profiles.reduce((sum, row) => sum + row.eligible_view_count, 0),
    reliable_dated_video_records: result.profiles.reduce((sum, row) => sum + row.reliable_dated_view_count, 0),
    age_cohort_count: result.cohortBenchmarks.length,
    queue_evidence_rows: result.queueEvidence.length,
    existing_queue_scores_modified: false
  };
  console.log("[within-channel:analyze] Computation complete", summary);
  console.log("[within-channel:analyze] Sample profiles", result.profiles.slice(0, 5).map(row => ({
    channel: row.channel_name,
    sample: row.eligible_view_count,
    medianViews: row.median_views,
    p90Views: row.p90_views,
    top5Share: row.top_5_view_share,
    lifetimeCandidates: row.outlier_candidate_count,
    datedSample: row.reliable_dated_view_count,
    assessment: row.sample_assessment
  })));

  if (dryRun) {
    console.log("[within-channel:analyze] Dry run complete; no database tables or report files were written.");
  } else {
    const persisted = persistWithinChannelAnalysis(db, { runId, generatedAt, input, result, config: analysisConfig, summary });
    console.log("[within-channel:analyze] Persisted independent analytical layer", persisted);
    writeWithinChannelPerformanceReports(db, root, runId);
    console.log("[within-channel:analyze] Complete. Existing queue scores/ranks remain unchanged.");
  }
} catch (error) {
  console.error("[within-channel:analyze] Failed", { runId, database: databasePath, message: error.message, stack: error.stack });
  process.exitCode = 1;
} finally {
  db.close();
}

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.