Docs/Skills and code/src/
src/analysis/channel-metric-features.js
const finite = value => value !== null && value !== undefined && value !== "" && Number.isFinite(Number(value));
const numberOrNull = value => finite(value) ? Number(value) : null;
const safeRatio = (a, b) => finite(a) && finite(b) && Number(b) > 0 ? Number(a) / Number(b) : null;
const round = (value, digits = 3) => value == null || !Number.isFinite(value) ? null : Number(value.toFixed(digits));
export function median(values) {
const sorted = values.filter(Number.isFinite).sort((a, b) => a - b);
if (!sorted.length) return null;
const middle = Math.floor(sorted.length / 2);
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
}
export function percentile(values, p) {
const sorted = values.filter(Number.isFinite).sort((a, b) => a - b);
if (!sorted.length) return null;
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(p * sorted.length) - 1));
return sorted[index];
}
export function subscriberBand(value) {
if (!finite(value)) return "unknown";
const n = Number(value);
if (n < 1_000) return "under_1k";
if (n < 10_000) return "1k_9k";
if (n < 100_000) return "10k_99k";
if (n < 1_000_000) return "100k_999k";
return "1m_plus";
}
export function catalogueBand(value) {
if (!finite(value)) return "unknown";
const n = Number(value);
if (n < 10) return "under_10";
if (n < 50) return "10_49";
if (n < 200) return "50_199";
if (n < 1_000) return "200_999";
return "1000_plus";
}
export function ageBand(days) {
if (!finite(days)) return "unknown";
const n = Number(days);
if (n < 365) return "under_1y";
if (n < 365 * 3) return "1_3y";
if (n < 365 * 7) return "3_7y";
return "7y_plus";
}
export function evidenceBand(count) {
if (!finite(count)) return "unknown";
const n = Number(count);
if (n < 5) return "under_5";
if (n < 10) return "5_9";
if (n < 20) return "10_19";
return "20_plus";
}
function validDate(value) {
if (!value) return null;
const ms = Date.parse(value);
return Number.isFinite(ms) ? ms : null;
}
function exactDateVideo(video) {
const precision = String(video.published_at_precision ?? "").toLowerCase();
if (!(precision.startsWith("exact_") || precision === "exact" || precision === "date")) return null;
const date = validDate(video.published_at_normalized) ?? validDate(video.published_at);
return date;
}
export function calculateChannelMetrics(channel, videos, options = {}) {
const nowMs = options.nowMs ?? Date.now();
const linked = [...new Map((videos ?? []).filter(v => v?.video_id).map(v => [v.video_id, v])).values()];
const withViews = linked.filter(v => finite(v.view_count)).map(v => ({ ...v, numeric_views: Number(v.view_count) }));
const viewValues = withViews.map(v => v.numeric_views);
const sampleViewsTotal = viewValues.reduce((sum, value) => sum + value, 0);
const sortedViews = [...viewValues].sort((a, b) => b - a);
const topDecileCount = sortedViews.length ? Math.max(1, Math.ceil(sortedViews.length * 0.1)) : 0;
const topDecileViews = sortedViews.slice(0, topDecileCount).reduce((sum, value) => sum + value, 0);
const exactAgeAdjusted = withViews.map(video => {
const publishedMs = exactDateVideo(video);
if (publishedMs == null) return null;
const ageDays = Math.max(1, (nowMs - publishedMs) / 86_400_000);
return video.numeric_views / ageDays;
}).filter(Number.isFinite);
const joinedMs = validDate(channel.joined_date);
const channelAgeDays = joinedMs == null ? null : Math.max(0, (nowMs - joinedMs) / 86_400_000);
const subscriberCount = numberOrNull(channel.subscriber_count);
const declaredVideoCount = numberOrNull(channel.video_count);
const totalViewCount = numberOrNull(channel.total_view_count);
const subsPerUpload = safeRatio(subscriberCount, declaredVideoCount);
const viewsPerUpload = safeRatio(totalViewCount, declaredVideoCount);
const viewsPerSubscriber = safeRatio(totalViewCount, subscriberCount);
const uploadsPerYear = channelAgeDays != null && channelAgeDays >= 30 ? safeRatio(declaredVideoCount, channelAgeDays / 365.2425) : null;
const corpusCoverage = safeRatio(linked.length, declaredVideoCount);
const metadataFields = [channel.channel_id, channel.description, subscriberCount, declaredVideoCount, totalViewCount, channel.joined_date, channel.country, channel.handle, channel.thumbnail_url];
const metadataCompleteness = metadataFields.filter(value => value !== null && value !== undefined && value !== "").length / metadataFields.length;
const warnings = [];
if (channel.subscriber_count_precision === "abbreviated") warnings.push("subscriber_count_is_abbreviated_approximation");
if (channel.video_count_precision !== "exact_displayed") warnings.push("declared_video_count_not_exact_displayed");
if (channel.total_view_count_precision !== "exact_displayed") warnings.push("total_view_count_not_exact_displayed");
if (linked.length < 10) warnings.push("small_linked_video_sample");
if (declaredVideoCount != null && linked.length > declaredVideoCount) warnings.push("linked_corpus_exceeds_declared_catalogue");
if (channelAgeDays == null) warnings.push("channel_join_date_missing_or_unparseable");
if (!withViews.length) warnings.push("no_linked_videos_with_view_counts");
if (exactAgeAdjusted.length < 5) warnings.push("few_exact_dated_videos_for_age_adjusted_performance");
const evidenceCount = linked.length;
return {
channel_url: channel.channel_url,
channel_name: channel.name,
ecosystem_classification: channel.ecosystem_classification ?? "unknown",
subscriber_count: subscriberCount,
subscriber_count_precision: channel.subscriber_count_precision ?? "unknown",
subscriber_band: subscriberBand(subscriberCount),
declared_video_count: declaredVideoCount,
declared_video_count_precision: channel.video_count_precision ?? "unknown",
catalogue_band: catalogueBand(declaredVideoCount),
total_view_count: totalViewCount,
total_view_count_precision: channel.total_view_count_precision ?? "unknown",
joined_date: channel.joined_date ?? null,
channel_age_days: round(channelAgeDays, 1),
channel_age_band: ageBand(channelAgeDays),
subscribers_per_upload: round(subsPerUpload, 2),
lifetime_views_per_upload: round(viewsPerUpload, 2),
lifetime_views_per_subscriber: round(viewsPerSubscriber, 3),
declared_uploads_per_year_since_join: round(uploadsPerYear, 2),
linked_video_count: linked.length,
corpus_coverage_ratio: round(corpusCoverage, 4),
corpus_coverage_percent: round(corpusCoverage == null ? null : corpusCoverage * 100, 1),
evidence_band: evidenceBand(evidenceCount),
video_count_with_views: withViews.length,
performance_sample_size: withViews.length,
sampled_view_total: sampleViewsTotal,
video_median_views: round(median(viewValues), 1),
video_p90_views: round(percentile(viewValues, 0.9), 1),
top_decile_video_count: topDecileCount,
top_decile_view_share: round(sampleViewsTotal > 0 ? topDecileViews / sampleViewsTotal : null, 4),
exact_dated_video_count_for_age_adjusted_views: exactAgeAdjusted.length,
median_exact_dated_views_per_day: round(median(exactAgeAdjusted), 2),
metadata_completeness_percent: round(metadataCompleteness * 100, 1),
warnings,
metrics_version: "channel-metrics-v1"
};
}
export function csvCell(value) {
const text = Array.isArray(value) || (value && typeof value === "object") ? JSON.stringify(value) : value == null ? "" : String(value);
return /[",\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
}
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.