import { digestWorkOf, excludedDownloadIdSet, type ChannelSnapshot, } from "../controller/channelSnapshot"; // THE PER-CHANNEL WORK COUNTS, as pure functions over one channel's snapshot. // // These were the `actionable*Count` helpers in the editor's // `lib/actionable/loadActionable.ts`, each taking an `ActionableRow` and reading // only `row.snapshot`. They move here so the channel-row builder // (`./channelRow.ts`) and the actionable census count the SAME way — the // dashboard's Videos / Digest to do cells and the widget's "Needs work" strip // were each a hand copy of the census before. `loadActionable.ts` keeps its // `actionable*` names as one-line wrappers over these, so no caller moved. // // Every function takes `null`/`undefined` (a channel that has never been // reported) and answers 0, and every optional bucket defaults to 0 for a // snapshot written before the bucket existed. type Snap = ChannelSnapshot | null | undefined; // Counts that drive the actionable lists exclude IDs that the availability // check has flagged as deleted / members-only / private — those videos // can't be acted on, so they shouldn't inflate "needs attention" totals. // `undownloadedIds` is already filtered at snapshot generation time, but we // apply the filter again so a stale snapshot can't surface excluded IDs. function countActionable( snapshot: Snap, ids: readonly string[] | undefined, ): number { if (!snapshot || !ids) return 0; const excluded = excludedDownloadIdSet(snapshot); if (excluded.size === 0) return ids.length; let n = 0; for (const id of ids) if (!excluded.has(id)) n++; return n; } export function undownloadedCountOf(snapshot: Snap): number { return countActionable(snapshot, snapshot?.undownloadedIds); } // Videos the roster says we were told about, never downloaded, and that have // since left the listing. Deliberately NOT run through countActionable: the // availability exclusions are keyed on videos we have on disk, and these have no // dir at all. export function missingNeverFetchedCountOf(snapshot: Snap): number { return snapshot?.missingNeverFetched?.length ?? 0; } // Listed videos the metadata scan has neither read nor recently failed on — // the scan operation's backlog. Deliberately NOT run through countActionable: // these videos have no directory, so the availability exclusions (which are // keyed on what is on disk) cannot say anything about them. export function metadataScanCountOf(snapshot: Snap): number { return snapshot?.metadataScan?.unscanned ?? 0; } export function untranscribedCountOf(snapshot: Snap): number { return countActionable(snapshot, snapshot?.buckets.downloadedNoTranscript); } // Transcribed videos whose transcript is badly truncated (the audio download // stopped early). export function incompleteTranscriptCountOf(snapshot: Snap): number { return snapshot?.buckets.incompleteTranscript?.length ?? 0; } // Downloads the duration guard flagged as truncated at the source (short audio // kept on disk, not transcribed). export function shortAudioCountOf(snapshot: Snap): number { return snapshot?.buckets.shortAudio?.length ?? 0; } // Cleanup buckets are filtered by "do not clean" at snapshot-generation time, // so the length is the actionable count directly. export function cleanTranscribedCountOf(snapshot: Snap): number { return snapshot?.buckets.transcribedWithAudio?.length ?? 0; } export function cleanExtraFormatsCountOf(snapshot: Snap): number { return snapshot?.buckets.multipleAudioFormats?.length ?? 0; } // The digest band's `reachable`, per channel — read through digestWorkOf, the // registry's classification the runner uses (no transcript = blocked, stale // cues = deferred; neither is in it). buildBands.ts folds the same call. export function digestReachableCountOf(snapshot: Snap): number { return digestWorkOf(snapshot).reachable; } // "The model produced something a human should look at", which includes the // total failures that write no section and so are invisible to any count of // files. export function digestWarningsCountOf(snapshot: Snap): number { return snapshot?.buckets.digestWarnings?.length ?? 0; } // Estimated bytes each cleanup would reclaim (0 for snapshots written before // cleanupBytes existed). export function cleanTranscribedBytesOf(snapshot: Snap): number { return snapshot?.cleanupBytes?.transcribedWithAudio ?? 0; } export function cleanExtraFormatsBytesOf(snapshot: Snap): number { return snapshot?.cleanupBytes?.multipleAudioFormats ?? 0; }