Make a 90-Day Creator Activity Filter Reproducible
Separate publication time, capture time, and evaluation time so historical creator activity filters can be reproduced without pretending stale observations are current.
Suppose a creator CSV was captured three months ago. Later, someone opens the file, subtracts its latest-known publication date from the current clock, and labels the creator inactive. The arithmetic may be correct, but the conclusion is not. The file only knows what was observed when it was captured. The creator may have published since then.
A reproducible activity filter needs three different times:
| Field | Meaning |
|---|---|
publishedAt |
Publication or event time for the chosen eligible item |
capturedAt |
When that observation or sample was recorded |
evaluatedAt |
When a report or decision is generated |
These are generic teaching fields, not a claim about any product's CSV schema or implementation. Every timestamp and example below is synthetic and unrelated to a real channel.
The distinction matters because publication time and observation time answer different questions. If a snapshot was captured on 2026-11-01T12:00:00.000Z, then an activity classification for that snapshot should be evaluated relative to that capture time. Re-running the same historical data later should produce the same classification.
Using the current clock instead changes the question. It no longer asks, "Was the latest observed eligible publication within 90 days when this snapshot was captured?" It asks something about current activity using stale evidence. That is not a valid current-activity test.
Define the event before measuring it
For YouTube video metadata, the documented snippet.publishedAt field is a publication date and time. The documentation also describes cases where publication time differs from upload time, such as content uploaded privately and made public later. So I would not rename every publication timestamp to uploadedAt unless the source actually defines that meaning.
If an importer already receives a documented absolute publication instant, the classifier can work with that value. It should not try to infer an exact instant from a display label such as 1 month ago. A relative label is presentation text, not an exact timestamp or a documented rounding contract.
The caller also has to define which publications are eligible. If the observation covers a Videos-tab sample, then the result describes that sample. It is not proof that no Short or stream exists outside it.
Use a fixed duration policy
JavaScript Date represents an instant as milliseconds relative to the UTC epoch. Calendar months, local dates, and daylight-saving transitions are different concepts from a fixed elapsed duration.
For this example, the policy is exactly 90 * 24 hours. It is not "three calendar months" and it is not "90 local calendar days." Fixtures use explicit Z timestamps so the boundary is visible.
The classifier accepts Date objects that an importer has already constructed from documented absolute timestamps. It deliberately does not parse arbitrary strings. Its duration policy is also explicit: windowDays must be a finite non-negative number. Invalid types are rejected before multiplication, and a finite numeric value that overflows when converted to milliseconds is rejected too.
function classifyAtCapture({ publishedAt, capturedAt } = {}, windowDays = 90) {
const msPerDay = 24 * 60 * 60 * 1000;
if (
typeof windowDays !== "number" ||
!Number.isFinite(windowDays) ||
windowDays < 0
) {
throw new RangeError("windowDays must be a finite non-negative number");
}
const windowMs = windowDays * msPerDay;
if (!Number.isFinite(windowMs)) {
throw new RangeError("windowDays is too large for a finite millisecond duration");
}
const validDate = (value) =>
value instanceof Date && Number.isFinite(value.getTime());
if (!validDate(publishedAt) || !validDate(capturedAt)) {
return { status: "unknown", ageMs: null };
}
const ageMs = capturedAt.getTime() - publishedAt.getTime();
if (ageMs < 0) {
return { status: "unknown", ageMs: null };
}
return {
status: ageMs <= windowMs ? "within-window" : "older-than-window",
ageMs
};
}
There is no Math.floor before the comparison. Exactly 90 fixed days is inside the window. One millisecond beyond the boundary is outside it.
| Synthetic case | Result |
|---|---|
| Publication equals capture time | within-window |
| Publication exactly 90 fixed days before capture | within-window |
| Publication 90 days plus 1 ms before capture | older-than-window |
| Missing, null, invalid, or non-Date timestamp | unknown |
| Publication after capture | unknown |
Returning unknown is important. A missing or null date is not equivalent to an old publication. A malformed or non-Date value is not age zero. A publication timestamp after the capture instant indicates inconsistent inputs for this classifier, so silently clamping the age to zero would hide a data problem.
The function validates only its small teaching contract. Its instanceof Date check is intentionally part of this local importer contract, not a general cross-realm date validator. A valid JavaScript Date still does not prove that the upstream timestamp has the intended business meaning. The importer has to establish what the timestamp represents and which observations were included.
Historical reproducibility is not current freshness
Suppose a snapshot captured on November 1 records an eligible publication just outside the 90-day window. older-than-window means only that the latest publication known in that chosen snapshot was older than the policy boundary at capture time.
It does not prove that the creator is inactive today. To answer a current question, refresh the eligible sample, establish its coverage, record a new capturedAt, and classify that new snapshot. Keep evaluatedAt separately if the report also needs to record when the decision was generated.
That separation makes historical reports reproducible without converting stale observations into unsupported current claims.
Disclosure: I make and maintain Channel Finder, a Chrome extension for researching channels from public content. Its current Chrome Web Store page is here. It is currently free early access with usage limits.
Comments
Join the discussionNo comments yet. Be the first to comment.