Content Refresh Prioritization  ·  FlyRank ML Internship Capstone

When should a page be refreshed?

A precision-ranked approach to SEO content decay, tested against a transparent baseline on an honest, client-held-out split.

By Faith Amanze  ·  github.com/faith-amanze/content-refresh-prioritization
Abstract

Which of a content team's underperforming pages are worth the limited hours available to review them — and can a simple learned ranking beat a hand-written rule at picking them? Using a 30,000-page anonymized sample from FlyRank's SEO content warehouse (32 client accounts, one 90-day snapshot), I built a transparent baseline rule and three candidate models — logistic regression, a shallow decision tree, and a random forest — evaluated on an identical client-holdout split so no client's pages could leak between training and evaluation. A logistic regression model, scored only on features knowable before any outcome window, ranked the top 50 highest-priority pages at 74.0% precision, against 48.0% for the hand-written rule and a 51.7% base rate — while a naive random 80/20 split inflated that same model's numbers by 8–10 points, a gap this paper measures directly rather than assumes away. The result is a ranked, reason-coded action playbook for a content team's review queue — a decision-support tool, not a certainty score, with its limits stated as plainly as its results.

Measured CTR by search position, this dataset (n=28,795 pages with valid position data) — the strongest, most confirmed signal in this study.
01 — Introduction

The queue is longer than the hours

A content team managing thousands of pages across dozens of client accounts cannot review every page every month. Someone has to decide which pages go into this week's refresh queue — and today, that decision is either a gut call or a rigid hand-written rule.

The unit of analysis here is a single page. The decision this work supports: which pages should a content strategist or SEO analyst spend their limited review hours on this cycle? A wrong call cuts two ways. A missed decliner (a false negative) keeps losing traffic quietly, a cost that's real but hard to see. A false alarm (a false positive) burns a scarce review hour on a page that didn't need it. Since review time is the constrained resource, this paper treats Precision@50 — of the top 50 pages ranked highest-priority, what fraction genuinely deserve review — as the metric that matches the actual decision, not overall accuracy across all 30,000 pages.

02 — Data

One snapshot, thirty thousand pages

This analysis uses the anonymized starter sample released for the FlyRank ML internship: 30,000 content pages across 32 of the warehouse's 104 total client accounts, one row per page, describing a single trailing 90-day window. The full internship warehouse (~79 million rows, hosted separately via Hugging Face) was not used here — extending these findings to that full release is future work, not a claim this paper makes.

Fields fall into four buckets: features (25 columns — traffic, engagement, position, content, and keyword-economics fields, all knowable before any outcome window), label (trend_direction/trend_pct, defining whether a page is "declining"), context (pseudonymous content_id/client_id, used only for grouping, never as model inputs), and excluded — 15 columns deliberately left out, most importantly the *_last_30d/*_prev_30d family, since the label is defined directly from a comparison of those two windows and including them would let a model reconstruct the label rather than learn a real pattern.

Public-safety note: every identifier in this dataset (page and client) is a pseudonym generated for this release. No real client names, URLs, or search queries appear anywhere in this analysis, its notebooks, or this page.
03 — Methodology

What counts as declining, and how it's checked

Target: a page is labeled "declining" when trend_direction == "down" — 54.2% of pages in this sample carry that label. This is an observed signal from the data, not a rule I invented.

Baseline: a transparent, hand-written rule — a page is worth reviewing if it ranks in a weak position (11th or worse) and has real search visibility (≥500 impressions in 90 days). No model, no training, fully auditable in one sentence.

Validation design: a grouped, client-holdout split (75/25 by client, not by row) — every page from a given client sits entirely on one side of the split. Splitting by row instead would let a model partly learn a client's own traffic quirks rather than a signal that generalizes to a client it has never seen.

Confirmed A naive random 80/20 split — the kind used without disclosure in some published SEO research — inflates this exact model's accuracy by +8.0 points and ROC AUC by +9.5 points compared to the honest grouped split. See Section 05 for the full comparison.

Leakage check: the "attack your own model" test — refit the model with a suspect column added back in, and watch for a suspicious jump. Adding trend_pct (the label's own source) back in pushes ROC AUC from 0.608 to 0.999 — a textbook confession. Adding the last_30d/prev_30d window family back in pushes it to 0.756 — a real, smaller leak. Both confirm the exclusions above were correct, not just assumed.

04 — Results

Every model beats the rule — modestly, and on the same ground

All three candidate models were evaluated against the baseline rule on the identical held-out test rows — 7,115 pages from 8 client accounts none of the models saw during training.

Bar chart comparing Precision@50 across base rate, baseline rule, random forest, decision tree, and logistic regression
Logistic regression leads on Precision@50 despite random forest scoring a marginally higher ROC AUC (0.608 vs 0.600) — a reminder that the metric matching the real decision (top-of-queue precision) doesn't always favor the most complex model.
ModelPrecision@50ROC AUCAvg. precision
Base rate (test set)0.517——
Baseline rule (hand-written)0.4800.4860.507
Random forest0.5000.6080.591
Decision tree (depth 4)0.6400.5970.582
Logistic regression0.7400.6000.605

Where the winning model is wrong: on the held-out set, 30.5% of pages it flags as declining aren't (false positives), and it misses 13.2% of actual decliners (false negatives). The false positives share a pattern: several are pages in good positions (4–9) trending "up," with a CTR reading of exactly 0.00% — low absolute traffic swinging to a zero-click reading by chance, which the model misreads as decline. The false negatives skew toward very low-traffic pages with almost nothing to detect a decline from in the first place.

Permutation importance (scored on average precision, since the task is ranking, not a plain yes/no call) shows days_with_impressions, days_with_sessions, and avg_position as the strongest drivers — the model leans on how consistently a page shows up, not just raw traffic totals.

Supporting signal The position→CTR relationship shown in the hero strip above is the single strongest, most confirmed pattern in this dataset: CTR falls from 2.71% at positions 1–3 to 0.15% at position 50+, monotonically, across sample sizes from n=1,141 to n=11,842.
05 — Methodology in practice: the split that lied

The gap a disclosed split closes

Reading a separate research paper built on this same warehouse, I noticed its methodology page described an "80/20 split" for its models with no mention of grouping by brand, across a sample spanning 57 brands. That's the same trap a row-level split creates: a model can partly learn brand-specific quirks rather than a signal that generalizes. Rather than take that as a hypothesis, I tested it directly on my own model.

Grouped bar chart comparing accuracy, ROC AUC, and Precision@50 between a naive random split and an honest grouped client-holdout split
Same model, same features, same data — the only thing that changed is whether client accounts were allowed to appear on both sides of the split. A naive split isn't dishonest by intent, but it is a source of measurable, avoidable overstatement.

A random 80/20 split (no grouping) inflates accuracy by 8.0 points and ROC AUC by 9.5 points over the grouped, client-honest split — on the exact same model. This doesn't mean any other paper's specific numbers are wrong; it means a split's grouping strategy is not a footnote, it's load-bearing, and this paper reports which one it used at every step above.

06 — Limitations

What this work cannot claim

Not causal. Every result here is observed and directional. Nothing in this paper establishes that refreshing a flagged page causes improved performance — that would require an experiment (e.g. a controlled before/after refresh test), which this study does not run.
A modest model, not a verdict. ROC AUC of 0.608 is a real, measured lift over the baseline's 0.486 — and also far from a reliable per-page certainty. Roughly 1 in 4 of even the top-ranked recommendations will be wrong on inspection. That's expected, not a flaw to hide.
Partial, single-snapshot coverage. 32 of 104 total client accounts, one 90-day window. This is not a live feed, and results may shift on a different time window or the full client population.
"Declining" ≠ "worst performer." Pages trending "down" have the lowest average CTR of any trend group (0.32%) — but pages trending "up" actually have the worst average position (22.5) of any group. The label captures recent trajectory, not overall page health; a page can be trending up while still ranking badly, and this playbook alone won't catch that combination.
07 — Ranked recommendations

A five-tier action playbook, not a single score

Every page in the dataset is assigned one action and one reason code, combining the baseline rule, the model's decline probability, and a content-type carve-out (feedly-sourced articles decline at 28.7% versus 56%+ for keyword/comparison articles, and warrant a separate review lens).

PRIORITY_REFRESH
Weak position + real visibility + model agrees it's declining — the strongest, most trustworthy tier.
4,560
REVIEW_CTR
Weak position + visibility, baseline signal alone (model unsure) — the fallback tier.
3,976
FEEDLY_REVIEW_SEPARATE
Aggregated content the model flags as declining — needs its own review lens, not this rule.
96
MONITOR_LOW_VOLUME
Model flags decline, but too little traffic to act on confidently — watch, don't act yet.
3,592
NO_ACTION
No strong signal, or no position data to act on at all.
17,776
Horizontal bar chart showing the number of pages assigned to each action tier
Pages per action tier, this snapshot.

What a human must check before acting

25.0% of PRIORITY_REFRESH pages sit under 1,000 impressions in 90 days — precisely the low-volume profile shown in Section 04 to produce false positives. These need a person's sanity check, not an automated trigger. And 1,847 pages trend "up" while still ranking 20th or worse — worth a look, even though this playbook doesn't flag them.

What should never be automated

No automatic publishing, rewriting, redirecting, or deleting from this queue. No merging or consolidating pages without a human confirming they target the same intent. No treating a "declining" label alone as proof a page is the worst performer — Section 06 shows why that assumption breaks.

Monitoring & retrain triggers

Re-check Precision@50 on every fresh snapshot — if it falls to or below the 0.480 rule-baseline, the model has stopped earning its complexity. If the declining base rate moves more than ~10 points from 0.542, or the action-tier mix more than doubles or halves between snapshots, treat that as a signal to investigate before trusting the new queue.